Make local LM Studio and Ollama Docker-friendly

Add shipped local-provider defaults for LM Studio and Ollama so Dockerized Agent Zero can reach host-running services without manual api_base edits. Map host.docker.internal in the sample Compose file for Linux Docker, document the behavior, and cover the provider defaults and compose mapping with focused regression tests.

Alessandro committed Jun 15, 2026 at 04:17 UTC c38808259b0623b527da87d623d8e4a5e695472e
4 files changed +82 -4
conf/model_providers.yaml
+11 -1
@@ -14,7 +14,7 @@
14 #
15 # Optional fields:
16 # kwargs: A dictionary of extra parameters to pass to LiteLLM.
17 -# This is useful for `api_base`, `extra_headers`, etc.
17 +# This is useful for `api_base`, `extra_headers`, non-secret local placeholders, etc.
18 #
19 # Optional model listing fields (used by the Model Configuration plugin):
20 # models_list:
@@ -83,6 +83,9 @@ chat:
83 models_list:
84 endpoint_url: "/v1/models"
85 default_base: "http://host.docker.internal:1234"
86 + kwargs:
87 + api_base: "http://host.docker.internal:1234/v1"
88 + api_key: "lm-studio"
89 mistral:
90 name: Mistral AI
91 litellm_provider: mistral
@@ -107,6 +110,8 @@ chat:
110 endpoint_url: "/api/tags"
111 format: "ollama"
112 default_base: "http://host.docker.internal:11434"
113 + kwargs:
114 + api_base: "http://host.docker.internal:11434"
115 ollama_cloud:
116 name: Ollama Cloud
117 litellm_provider: openai
@@ -186,12 +191,17 @@ embedding:
191 lm_studio:
192 name: LM Studio
193 litellm_provider: lm_studio
194 + kwargs:
195 + api_base: "http://host.docker.internal:1234/v1"
196 + api_key: "lm-studio"
197 mistral:
198 name: Mistral AI
199 litellm_provider: mistral
200 ollama:
201 name: Ollama
202 litellm_provider: ollama
203 + kwargs:
204 + api_base: "http://host.docker.internal:11434"
205 openai:
206 name: OpenAI
207 litellm_provider: openai
docker/run/docker-compose.yml
+3 -1
@@ -5,4 +5,6 @@ services:
5 volumes:
6 - ./agent-zero:/a0
7 ports:
8 - - "50080:80"
\ No newline at end of file
8 + - "50080:80"
9 + extra_hosts:
10 + - "host.docker.internal:host-gateway"
docs/setup/installation.md
+2 -2
@@ -527,13 +527,13 @@ Replace `<model-name>` with the name of the model you want to use. For example:
527 1. Once you've downloaded your model(s), select it in the Settings page of the GUI.
528 2. Within the Chat model, Utility model, or Embedding model section, choose **Ollama** as provider.
529 3. Write your model code as expected by Ollama, in the format `llama3.2` or `qwen2.5:7b`
530 -4. Provide your API base URL to your Ollama API endpoint, usually `http://host.docker.internal:11434`
530 +4. Agent Zero includes Docker-friendly defaults for Ollama on the host at `http://host.docker.internal:11434`. Override the API base URL only if your Ollama server runs somewhere else.
531 5. Click `Save` to confirm your settings.
532
533 ![ollama](../res/setup/settings/4-local-models.png)
534
535 > [!NOTE]
536 -> If Agent Zero runs in Docker and Ollama runs on the host, ensure port **11434** is reachable from the container. If both services are in the same Docker network, you can use `http://<container_name>:11434` instead of `host.docker.internal`.
536 +> If Agent Zero runs in Docker and Ollama runs on the host, ensure port **11434** is reachable from the container. The shipped Docker Compose file maps `host.docker.internal` to the host gateway for Linux Docker. If both services are in the same Docker network, you can use `http://<container_name>:11434` instead of `host.docker.internal`.
537
538 ### Managing Downloaded Models
539
tests/test_model_config_api_keys.py
+66
@@ -276,3 +276,69 @@ def test_provider_key_modes_for_local_and_ollama_cloud():
276 assert model_config.provider_requires_api_key("lm_studio") is False
277 assert model_config.provider_requires_api_key("other") is False
278 assert model_config.provider_requires_api_key("ollama_cloud") is True
279 +
280 +
281 +def test_local_provider_defaults_are_docker_friendly():
282 + import yaml
283 +
284 + provider_path = PROJECT_ROOT / "conf" / "model_providers.yaml"
285 + provider_config = yaml.safe_load(provider_path.read_text(encoding="utf-8"))
286 +
287 + assert provider_config["chat"]["lm_studio"]["kwargs"]["api_base"] == (
288 + "http://host.docker.internal:1234/v1"
289 + )
290 + assert provider_config["chat"]["lm_studio"]["kwargs"]["api_key"] == "lm-studio"
291 + assert provider_config["chat"]["lm_studio"]["models_list"]["default_base"] == (
292 + "http://host.docker.internal:1234"
293 + )
294 + assert provider_config["chat"]["ollama"]["kwargs"]["api_base"] == (
295 + "http://host.docker.internal:11434"
296 + )
297 + assert provider_config["chat"]["ollama"]["models_list"]["default_base"] == (
298 + "http://host.docker.internal:11434"
299 + )
300 + assert provider_config["embedding"]["lm_studio"]["kwargs"]["api_base"] == (
301 + "http://host.docker.internal:1234/v1"
302 + )
303 + assert provider_config["embedding"]["lm_studio"]["kwargs"]["api_key"] == "lm-studio"
304 + assert provider_config["embedding"]["ollama"]["kwargs"]["api_base"] == (
305 + "http://host.docker.internal:11434"
306 + )
307 +
308 +
309 +def test_local_provider_runtime_defaults_and_overrides(monkeypatch):
310 + monkeypatch.setattr(models, "get_api_key", lambda provider: "None")
311 +
312 + lm_chat = models.get_chat_model("lm_studio", "local-chat-model")
313 + assert lm_chat.model_name == "lm_studio/local-chat-model"
314 + assert lm_chat.kwargs["api_base"] == "http://host.docker.internal:1234/v1"
315 + assert lm_chat.kwargs["api_key"] == "lm-studio"
316 +
317 + lm_embedding = models.get_embedding_model("lm_studio", "nomic-embed-text")
318 + assert lm_embedding.model_name == "lm_studio/nomic-embed-text"
319 + assert lm_embedding.kwargs["api_base"] == "http://host.docker.internal:1234/v1"
320 + assert lm_embedding.kwargs["api_key"] == "lm-studio"
321 +
322 + custom_lm_embedding = models.get_embedding_model(
323 + "lm_studio",
324 + "nomic-embed-text",
325 + api_base="http://127.0.0.1:1234/v1",
326 + api_key="real-local-key",
327 + )
328 + assert custom_lm_embedding.kwargs["api_base"] == "http://127.0.0.1:1234/v1"
329 + assert custom_lm_embedding.kwargs["api_key"] == "real-local-key"
330 +
331 + ollama_embedding = models.get_embedding_model("ollama", "nomic-embed-text")
332 + assert ollama_embedding.model_name == "ollama/nomic-embed-text"
333 + assert ollama_embedding.kwargs["api_base"] == "http://host.docker.internal:11434"
334 + assert "api_key" not in ollama_embedding.kwargs
335 +
336 +
337 +def test_docker_compose_maps_host_docker_internal_for_local_models():
338 + import yaml
339 +
340 + compose_path = PROJECT_ROOT / "docker" / "run" / "docker-compose.yml"
341 + compose = yaml.safe_load(compose_path.read_text(encoding="utf-8"))
342 + service = compose["services"]["agent-zero"]
343 +
344 + assert "host.docker.internal:host-gateway" in service["extra_hosts"]