_ _ _
| | ___ ___ __ _| |_ __ ___ _ _| |_ ___ _ __
| |/ _ \ / __/ _` | | '__/ _ \| | | | __/ _ \ '__|
| | (_) | (_| (_| | | | | (_) | |_| | || __/ |
|_|\___/ \___\__,_|_|_| \___/ \__,_|\__\___|_|
localhost ──▶ remote ollama
localrouter is an OpenCode companion for using a remote Ollama machine as
if it were running on your laptop.
localrouter forwards normal Ollama traffic to the remote host and intercepts
/api/chat and /api/generate so it can confirm the requested model is
installed first. If auto_pull is enabled, localrouter asks the remote
Ollama host to pull the model and then resumes the original request.
For machines that are not on the same network, localrouter can also open
an SSH local forward itself and route all Ollama traffic through that tunnel.
- Prepare the Ollama host:
- Install
localrouteron the client machine: - Configure localrouter and OpenCode together:
localrouter init-opencode \ --remote http://HOST_IP:11434 \ --model qwen2.5-coder:7b localrouter start opencode
- In OpenCode, run
/modelsand selectlocalrouter.
init-opencode writes localrouter's config and adds a localrouter provider
to ~/.config/opencode/opencode.json.
To switch models later:
localrouter use llama3.1:8b
If OpenCode has the localrouter model selected, the next prompt uses the new
model without restarting OpenCode. localrouter rewrites that stable alias to
the current default_model on each request and pulls the model if needed.
You can also choose concrete models from OpenCode's /models list. Those
requests are forwarded with the selected model and auto-pulled when missing.
See the focused OpenCode guide for generated config, SSH mode, and troubleshooting.
If the Ollama host is reachable over SSH but not directly over the LAN/VPN, use the built-in tunnel mode:
localrouter init-opencode \ --ssh user@HOSTNAME:22 \ --ssh-remote-addr 127.0.0.1:11434 \ --model qwen2.5-coder:7b localrouter start opencode
This starts ssh -N -L ... under the hood and points the proxy at the local
end of that forward automatically. It assumes ssh is installed locally and
that key-based auth or an SSH agent is already configured.
| Task | Pipeline |
|---|---|
| First interactive setup | localrouter init-config && localrouter serve |
| First scripted setup | localrouter init-config --remote http://HOST_IP:11434 --default-model qwen2.5-coder:7b --auto-pull=true && localrouter serve |
| First scripted off-network setup | localrouter init-config --ssh user@HOSTNAME --ssh-remote-addr 127.0.0.1:11434 --default-model qwen2.5-coder:7b --auto-pull=true && localrouter serve |
| Update an existing config in place | localrouter init-config --force --remote http://HOST_IP:11434 --default-model qwen2.5-coder:7b --auto-pull=true |
| Background start | localrouter start && localrouter status |
| Background restart | localrouter restart && localrouter status |
| Stop background proxy | localrouter stop |
| Print resolved config paths and values | localrouter config |
| Show configured plus installed models | localrouter list |
| Show only models installed on the remote | localrouter list --installed |
| Inspect one remote model | localrouter info qwen2.5-coder:7b |
| Pull one model on the remote now | localrouter pull qwen2.5-coder:7b |
| Persist a default model | localrouter use qwen2.5-coder:7b |
| Persist a default model and start | localrouter use qwen2.5-coder:7b --start |
| Configure localrouter plus OpenCode | localrouter init-opencode --remote http://HOST_IP:11434 --model qwen2.5-coder:7b |
| Health check the proxy itself | curl http://localhost:11434/__localrouter/healthz |
| Interactive picker and background start | localrouter |
git clone https://github.com/fede-h/localrouter && cd localrouter && PREFIX="$HOME/.local" ./install.sh && "$HOME/.local/bin/localrouter" version
git clone https://github.com/fede-h/localrouter && cd localrouter && sudo ./install.sh && localrouter version
Requires Go 1.22 or newer.
git clone https://github.com/fede-h/localrouter && cd localrouter && mkdir -p dist && go build -trimpath -o dist/localrouter ./cmd/localrouter && ./dist/localrouter version
mkdir -p dist && GOOS=linux GOARCH=amd64 go build -trimpath -o dist/localrouter-linux-amd64 ./cmd/localrouter && GOOS=linux GOARCH=arm64 go build -trimpath -o dist/localrouter-linux-arm64 ./cmd/localrouter && GOOS=darwin GOARCH=arm64 go build -trimpath -o dist/localrouter-darwin-arm64 ./cmd/localrouter && GOOS=darwin GOARCH=amd64 go build -trimpath -o dist/localrouter-darwin-amd64 ./cmd/localrouter && GOOS=windows GOARCH=amd64 go build -trimpath -o dist/localrouter-windows-amd64.exe ./cmd/localrouter
localrouter config prints the exact paths for the current user. By default
the config lives under os.UserConfigDir() and runtime state lives under
os.UserCacheDir().
{
"listen_addr": "localhost:11434",
"remote_url": "http://192.168.1.50:11434",
"ssh_target": "",
"ssh_remote_addr": "127.0.0.1:11434",
"auto_pull": true,
"pull_timeout_secs": 1800,
"reach_timeout_ms": 1500,
"default_model": "qwen2.5-coder:7b"
}| Field | Meaning |
|---|---|
listen_addr |
Local bind address for the proxy. |
remote_url |
Base URL for the remote Ollama host when it is directly reachable. |
ssh_target |
Optional SSH target in [user@]host[:port] form for off-network access. |
ssh_remote_addr |
Host:port reached from the SSH host, usually 127.0.0.1:11434. |
auto_pull |
Pull missing models on demand before forwarding the request. |
pull_timeout_secs |
Maximum duration for one remote pull. |
reach_timeout_ms |
Timeout for TCP reachability checks in status. |
default_model |
Model remembered by localrouter use and the no-arg interactive flow. |
models.list sits next to config.json and is a plain text file with one
model tag per line:
qwen2.5-coder:7b
llama3.1:8b
mistral:7b
These override the current run only and are never written back to
config.json.
| Variable | Effect |
|---|---|
LOCALROUTER_LISTEN |
Override listen_addr |
LOCALROUTER_REMOTE |
Override remote_url |
LOCALROUTER_SSH |
Override ssh_target |
LOCALROUTER_SSH_REMOTE_ADDR |
Override ssh_remote_addr |
LOCALROUTER_AUTO_PULL |
Override auto_pull |
LOCALROUTER_DEFAULT_MODEL |
Override default_model |
LOCALROUTER_PULL_TIMEOUT |
Override pull timeout in seconds |
LOCALROUTER_REACH_TIMEOUT_MS |
Override reachability timeout in milliseconds |
For every POST /api/chat, POST /api/generate, and
POST /v1/chat/completions, localrouter:
- Reads and restores the request body.
- Extracts the
modelfield if present. - Rewrites the stable OpenCode model alias
localrouterto the currentdefault_model, if used. - Calls the remote
/api/tags. - Forwards immediately if the model already exists.
- Returns
404if the model is missing andauto_pull=false. - Calls remote
/api/pullif the model is missing andauto_pull=true. - Returns
502if the remote becomes unreachable. - Returns
500if the remote pull itself fails.
All other Ollama paths are forwarded as-is. The proxy also exposes:
curl http://localhost:11434/__localrouter/healthz
Example response:
{"ok":true,"service":"localrouter","remote":"http://192.168.1.50:11434","auto_pull":true}localrouterbinds tolocalhost:11434by default. Keep it that way unless you intentionally want to expose the proxy itself.- For untrusted or off-network links, prefer the built-in SSH tunnel mode or put TLS/WireGuard in front of Ollama.
- Exposing Ollama on
0.0.0.0:11434means anything on that reachable network can hit its API unless you protect it elsewhere.