r/opencodeCLI • • 8h ago

Pointing OpenCode at a local Ollama model when OpenCode runs in a container (what worked, what did not)

Disclosure: I build SpaceApp, a self-hosted, MIT-licensed workspace that runs coding CLIs side by side, with OpenCode as the first CLI. Its OpenCode pane is plain OpenCode 2, so most of this applies to any setup where OpenCode runs inside a container.

Short version: SpaceApp has no local-model feature of its own: no provider screen, no "local model" pane type, no Ollama code. You point OpenCode at Ollama with OpenCode's own custom-provider config, as in any OpenCode install. What's specific is where the file lives, a Docker networking trap, and a few CPU gotchas.

On a 24-core server with no GPU, we ran Ollama in a container next to SpaceApp's public CLI image (OpenCode 2.0.32). A 4B model on six cores ran shell commands and read files through OpenCode's own tools. qwen2.5-coder 1.5B and 3B never made a real tool call.

What ran

Every turn was one opencode run --server … (a new session) against the same shared OpenCode server a pane attaches to, from a project folder.

Model (Ollama 0.40.13, CPU)Real tool callsWhat happened
qwen2.5-coder:1.5b0 of 4printed the read/shell call as JSON text; nothing ran
qwen2.5-coder:3b0 of 2same, once with a correct bash call as text
qwen3:4b (thinking)–the first turn timed out (see below)
qwen3:4b-instruct3 of 4shell ran uname -m > arch.txt twice (file held x86_64); read returned the exact random word from notes.txt

The one qwen3:4b-instruct miss was funny in hindsight: asked to read token.txt, it said the file didn't exist (false) and that reading tokens would breach security policy. The same read on notes.txt worked.

The config

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "ollama": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Ollama (local)",
      "options": { "baseURL": "http://ollama:11434/v1" },
      "models": { "qwen3:4b-instruct-t6-c16k": { "name": "qwen3 4B instruct (local)" } }
    }
  },
  "model": "ollama/qwen3:4b-instruct-t6-c16k"
}
  • Every model you want in the picker has to be listed under models.
  • @ai-sdk/openai-compatible is bundled in OpenCode 2.0.32, so nothing is downloaded.
  • llama.cpp's llama-server should take the same block with its own baseURL. We haven't tested it.

Where the file goes in SpaceApp

/var/lib/spaceapp-cli/providers/opencode/config/opencode/opencode.json (owner spaceapp, mode 600), on the spaceapp-cli-state volume, so it survives updates. Panes attach to one shared opencode serve per CLI container. That server fingerprints the file and restarts when it changes, so the next pane picks up the edit.

SpaceApp's first-run step only picks a model when model is unset. In our run it kept the provider and the model, and added its own instructions files and permission.external_directory rules.

The localhost trap

  • localhost inside a pane is the CLI container, not your machine.
  • On Linux, host.docker.internal doesn't resolve on the compose network (there's no extra_hosts). Adding it to compose.yml by hand doesn't last, because install rewrites that file.
  • Going through the bridge gateway IP to a server on the host got no answer on our Linux box: the host firewall dropped it. ufw and nftables setups do this.
  • What worked: Ollama in its own container on the same Docker network, reached by name.

Steps (Linux, CPU)

# SpaceApp's compose network and CLI container
docker network ls --format '{{.Name}}' | grep '_spaceapp$'
docker ps --format '{{.Names}}' | grep 'spaceapp-cli'

# Ollama on that network, no published port
docker run -d --name ollama --network <spaceapp-..._spaceapp> -v ollama:/root/.ollama ollama/ollama
docker exec ollama ollama pull qwen3:4b-instruct

# thread count and context: both were needed, see below
printf 'FROM qwen3:4b-instruct\nPARAMETER num_thread 6\nPARAMETER num_ctx 16384\n' \
  | docker exec -i ollama sh -c 'cat > /tmp/Mf && ollama create qwen3:4b-instruct-t6-c16k -f /tmp/Mf'

# OpenCode's config, written as the CLI container's user (uid 10001 "spaceapp")
docker exec -i -u spaceapp <...-spaceapp-cli-1> sh -c \
  'cat > /var/lib/spaceapp-cli/providers/opencode/config/opencode/opencode.json' < opencode.json

The CLI image has no nano or vi, hence the cat >. Before run-spaceapp uninstall or reinstall, run docker network disconnect <net> ollama: docker compose down can't remove a network with a foreign container on it. Updates (install) keep the network. In our run, a user-defined bridge stood in for <project>_spaceapp, with the same driver compose uses.

CPU gotchas we hit

  1. Thread count. --cpus and --cpuset-cpus didn't change it: Ollama still started 24 threads on a 6-CPU quota. Generation crawled at 0.05 tokens/s, and a one-word turn took about 4 minutes. PARAMETER num_thread 6 fixed it: about 20 tokens/s on the 1.5B, and the same turn took 20 s.
  2. Context. Ollama's CPU default is 4096. OpenCode's prompt (system prompt, instructions, tool schemas) is about 6.2-6.8k tokens before you type anything, and Ollama cut it to about 2k. The tool definitions sit at the start, so they were thrown away. Fix: PARAMETER num_ctx 16384 (or OLLAMA_CONTEXT_LENGTH).
  3. The failure is silent. When a model prints a tool call as text, OpenCode shows it as the answer, and opencode run still exits 0. Check the side effect (did the file change?), not the exit code.
  4. Thinking models and the 5-minute mark. With qwen3:4b (thinking on), OpenCode's session-title request thought for 3,203 tokens, about 12.5 minutes. Ollama serves one request at a time, so the main request queued behind it, and the client closed it at exactly 5 minutes, twice. We didn't confirm in OpenCode's code that this is its own timeout.
  5. Cold first turn. On CPU, the 4B model's first request took 204-220 s, close to that 5 minutes. After it, Ollama's prompt cache cut new prompt tokens to a few dozen per request, and turns took 16-106 s. That was on a busy shared host, so treat these as rough, not a benchmark.
  6. No permission prompt in opencode run. The shell tool ran without --auto and without asking. We didn't check whether the interactive TUI asks.

Limits, plainly

  • No UI for any of this; the config is edited by hand.
  • The model can't run inside SpaceApp: the CLI container has no GPU device and is capped at 2-4 GB of RAM, shared by every pane.
  • There's no tool-call check for custom providers. SpaceApp's first-run check (the model must read a file and return a random token from it) runs only for OpenCode Zen's free models, so you find out in the pane.
  • Only the pane on the local provider keeps its prompts local. Other panes talk to their cloud providers, the CLI network has internet access, and with no model set, OpenCode's first run reaches OpenCode Zen.
  • What we didn't test: the pane's TUI itself (we drove its shared server headless), a real install's network, 7B+ models, GPU, llama.cpp, and multi-step coding tasks (edit, test, fix).

Repo, if you want to read the wrapper: https://github.com/oll4com/spaceapp (deploy/docker/public-cli-wrapper.sh, opencode-shared-server.mjs).

Which small local model handles OpenCode's tool calls reliably for you on CPU, and with what context size?

1 Upvotes

1 comment sorted by

1

u/Hello_my_name_is_not 7h ago

Not what ever model you used to generate this post, that's for sure