Guide
How do I run OpenCode as a server on a remote computer?
opencode serve runs OpenCode as a headless HTTP server with an OpenAPI spec, and the OpenCode SDK and other clients connect to it. On your own server you keep it on loopback and reach it through an SSH tunnel, or turn on its password. On Sanbox, an OpenCode Computer runs opencode serve privately for you and stays running until you stop it; sanbox opencode connect gives your application a short-lived URL and token for the native SDK, and no port is opened to the internet.
Run this with your agent
Read https://sanbox.cloud/kb/coding-agents/opencode-server-on-a-remote-computer.md and follow it to run an OpenCode server on a Computer and connect the SDK.
If the Sanbox CLI is missing, install it with `npm install -g @sanlabs/sanbox-cli@latest`
and sign in with `sanbox login`. Show me each command before you create, stop or delete a Computer.
How does it work on your own server?
By default opencode serve listens on 127.0.0.1 port 4096, and publishes its OpenAPI spec at /doc. Keep it on loopback and forward the port over SSH:
opencode serve --hostname 127.0.0.1 --port 4096
ssh -L 4096:127.0.0.1:4096 <user>@<server>
Your client then uses http://localhost:4096. If the server has to listen on another address, set OPENCODE_SERVER_PASSWORD to turn on HTTP basic auth (user opencode unless you set OPENCODE_SERVER_USERNAME), and put TLS in front, since basic auth over plain HTTP sends the password readably. You also keep the machine patched and running.
Steps on Sanbox
1. Create an OpenCode Computer template
sanbox model-providers list --json
sanbox model-providers models <provider-id> --json
sanbox templates create --name "OpenCode server" --type opencode-computer \
--model-provider <provider-id> --model <model-id> \
--allowed-model <other-model-id> \
--network-policy policy.json --json
sanbox templates validate <template-id> --json
--model is the default; repeat --allowed-model for other models from the same provider that your app may switch to per prompt. Sanbox manages the network rules for the model provider. policy.json lists only the hosts your tasks need, such as github.com or registry.npmjs.org.
2. Start the Computer
sanbox run --template <template-id> --external-run-id <stable-computer-id> --json
sanbox runs get <run-id> --json
Give each Computer a stable --external-run-id and reuse it on retries, so a lost response doesn't start a second one. Without a task, Sanbox starts opencode serve and makes no model call; a task given with --task is sent once the server is ready. Add --model <model-id> to pick a different startup model from the template's list. Wait until the run is running, and don't use --wait: for a Computer it waits until it stops.
3. Create an SDK connection
sanbox opencode connect <run-id> --expires 1h --json
The response has data.url, data.access_token (shown once) and data.connection.id. The URL is an authenticated Sanbox proxy to opencode serve on the Computer's private loopback port 4096. Treat the token as a bearer secret: keep it in your application, and never pass your SANBOX_API_KEY to the OpenCode SDK.
4. Drive it from the SDK
Install the SDK with npm install @opencode-ai/sdk, then:
import { createOpencodeClient } from "@opencode-ai/sdk"
// connection: the parsed --json output from step 3
const client = createOpencodeClient({
baseUrl: connection.data.url,
headers: { Authorization: `Bearer ${connection.data.access_token}` },
})
const events = await client.event.subscribe()
const session = await client.session.create({})
if (!session.data) throw new Error("OpenCode session creation failed")
const sessionId = session.data.id
const done = (async () => {
for await (const event of events.stream) {
if (event.type === "session.idle" && event.properties.sessionID === sessionId) return
// forward message, tool, diff and permission events to your users
}
})()
await client.session.promptAsync({
path: { id: sessionId },
body: { parts: [{ type: "text", text: "Complete the requested task." }] },
})
await done
A later promptAsync on the same session steers the agent while it works. To use another allowed model for one prompt, pass body.model with the template's provider ID and the model ID; a Custom OpenAI Proxy uses providerID: "custom" inside the runtime.
5. Revoke and stop
sanbox opencode connections list <run-id> --json
sanbox opencode connections revoke <run-id> <connection-id> --json
sanbox runs stop <run-id> --wait --json
Several connections can be active at once. A token stops working when it expires, is revoked, or the runtime session ends. Stop and resume don't revive old tokens, so create a new connection after a resume.
What can the SDK connection reach?
Session APIs, SSE events, files, commands, session shells and permission responses work. Configuration and provider-credential routes, credential connection flows, raw PTY access, server upgrade or disposal, and public session shares return 403 opencode_route_denied. Your users can steer the agent but can't change its provider or keys.
What other ways in are there?
- The console chat, to talk to the same OpenCode server yourself.
- The console's browser terminal, which keeps running when you close the tab.
sanbox ssh <run-id>, a private SSH session with a temporary key (add--rootfor root).sanbox execandsanbox cpfor scripted commands and file copies.
Lock down egress
The model provider's rules are added and owned by Sanbox. Everything else is denied until policy.json allows it, as hosts and ports. A template holds at most 16 rules of its own. A blocked request names the host it couldn't reach.
Snapshot it
Prepare one Computer, stop it, and save it. New Computers from the snapshot start with the same files, packages and OpenCode history:
sanbox runs stop <run-id> --wait --json
sanbox snapshots create --run <run-id> --name opencode-base --wait
sanbox run --snapshot opencode-base --json
For faster starts, keep up to five ready Computers with sanbox pools create --snapshot opencode-base --name opencode-ready --size 2. Pool members use ordinary capacity. Connections don't carry over; create one per run.
What this doesn't do
- No public URL. Every request needs a connection token, and the URL is an API proxy for OpenCode, not a website.
--waitdoesn't wait for an answer. Read responses through the SDK events or the console.- Processes and RAM don't survive a stop; the disk does.
- The OpenCode 2 Computer type is in beta, and its SDK connection allows OpenCode v2's own routes, so code written for this page may need changes there.
Related
- How do I give each of my users their own persistent agent computer?
- How do I make agent computers start instantly?
- How do I use my own model endpoint with an agent computer?
- Can I watch my AI agent's browser live and take over?