Desktop Runners
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page was written against LM Studio 0.4 (the series that introduced the headless llmster daemon), Jan
v0.8.4, Docker Model Runner v1.2.8, LocalAI v4.10.0 and mlx-lm 0.31.3, with Python 3.12 (openai 3.20.0) and
Spring AI 2.0.1 for the clients, as checked on 2026-09-28 (see the Running LLMs
Locally baseline). Each tool’s documentation was read from its public source repository on that date.
Ollama is the default choice on this site, but it is not the only way to run a model on one machine. The runners below differ in who they are for: LM Studio and Jan are desktop apps with a chat UI, Docker Model Runner fits models into the Docker workflow, LocalAI is an all-in-one self-hosted API, and MLX-LM is Apple’s own engine for Apple silicon. All of them serve an OpenAI-compatible API, so the client code does not change.
LM Studio
LM Studio comes in three pieces that share one engine:
-
the desktop app — model search and download from the Hugging Face Hub, a chat UI, document chat, MCP servers and a local API server;
-
llmster, the headless daemon — the same engine without the GUI, for a Linux server, a GPU box without a screen, CI, or a service that starts at boot;
-
lms, the CLI, which drives either of them.
# headless install (Linux / macOS); the desktop app installs lms too
curl -fsSL https://lmstudio.ai/install.sh | bash
lms daemon up # start llmster
lms get openai/gpt-oss-20b # download a model
lms load openai/gpt-oss-20b # load it into memory
lms server start --port 1234 # OpenAI + Anthropic compatible server
lms ps # loaded models
lms log stream # watch requests and responses
The server listens on http://localhost:1234, binds to 127.0.0.1 by default (--bind 0.0.0.0 or "Serve on
Local Network" opens it up) and serves /v1/chat/completions, /v1/responses, /v1/embeddings and /v1/models
in the OpenAI shape, plus POST /v1/messages in the Anthropic shape. Authentication is off by default: enable
Require Authentication in the server settings and create an API token, which clients then send as
Authorization: Bearer (or x-api-key on the Anthropic endpoint). Enable it before binding to anything but
loopback. Books written before 2026 show LM Studio as an OpenAI-compatible server in its GUI-only form; the
headless daemon and the Anthropic endpoint are newer.
curl http://localhost:1234/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $LM_API_TOKEN" \
-d '{"model": "openai/gpt-oss-20b", "max_tokens": 256,
"messages": [{"role": "user", "content": "Say hello from LM Studio."}]}'
Jan
Jan is an open-source desktop chat app (built on llama.cpp) with a built-in Local API Server: Settings > Local
API Server > Start Server. It listens on http://127.0.0.1:1337 with an /v1 prefix and requires an API key if
you set one — set one, especially before switching the host to 0.0.0.0, which the docs call out as the
insecure option.
curl http://127.0.0.1:1337/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JAN_API_KEY" \
-d '{"model": "YOUR_MODEL_ID", "messages": [{"role": "user", "content": "Tell me a joke."}]}'
Docker Model Runner
Docker Model Runner (DMR) runs models as part of Docker Desktop or Docker Engine. Models are OCI artifacts
(ai/… on Docker Hub) or Hub GGUFs, pulled and run with docker model:
docker model pull ai/smollm2:360M-Q4_K_M
docker model run ai/smollm2 "What is a vector database?"
docker model pull hf.co/bartowski/Llama-3.2-1B-Instruct-GGUF
docker model ls
docker model configure --context-size 8192 ai/qwen2.5-coder
Three inference engines are available: llama.cpp (the default, on every platform), vLLM (Linux x86_64 and
Windows WSL2 with NVIDIA GPUs; installed with docker model install-runner --backend vllm --gpu cuda, models
usually tagged -vllm) and Diffusers for images. The llama.cpp engine defaults to a 4096-token context, so raise
it for RAG or coding use — the same pitfall as Ollama’s
context length.
The API is served in three shapes on the same port — TCP 12434 on the host (enabled by default on Docker
Engine, opt-in on Docker Desktop), and http://model-runner.docker.internal from containers on Docker Desktop:
| Client | Base URL |
|---|---|
OpenAI SDK / tools |
|
Anthropic SDK / tools |
|
Ollama-compatible tools |
|
The Model Runner API is not authenticated. Docker’s documentation states that any client that can reach it — including other containers on the same Docker network — can pull, load and run models and send inference requests. Keep host-side TCP disabled unless you need it, never publish port 12434 beyond loopback, and put an authenticating proxy in front of it for anything shared (see authentication in front of local servers).
In Docker Compose, models are a top-level element, and services that list a model receive its URL and name as
environment variables (LLM_URL, LLM_MODEL for a model called llm):
services:
docsassistant:
image: docsassistant:latest
models:
- llm
- embedding-model
models:
llm:
model: ai/qwen2.5-coder
context_size: 8192
embedding-model:
model: ai/all-minilm
That requires Docker Compose v2.38 or later. How Compose is used for the rest of a local development stack is covered on Compose for Local Development. See Docker Model Runner, DMR — API reference and Use AI models in Compose.
LocalAI
LocalAI is a self-hosted, OpenAI-compatible API server that bundles many backends (llama.cpp, vLLM, transformers, diffusers, speech and image models) behind one endpoint, with an Anthropic Messages endpoint and MCP support as well. It listens on port 8080:
docker run -ti --name local-ai -p 127.0.0.1:8080:8080 -e LOCALAI_API_KEY localai/localai:latest
# NVIDIA GPU: localai/localai:latest-gpu-nvidia-cuda-12 (or -cuda-13) with --gpus all
local-ai run llama-3.2-1b-instruct:q4_k_m # from the LocalAI gallery
local-ai run huggingface://TheBloke/phi-2-GGUF/phi-2.Q8_0.gguf
Setting LOCALAI_API_KEY (one key or a comma-separated list) turns on key authentication; without it the API is
open. See LocalAI — Documentation and
LocalAI — Authentication.
MLX-LM
On Apple silicon, Apple’s MLX framework runs models natively on unified memory, and mlx-lm is its LLM package:
generation, chat, fine-tuning, quantization, and a small OpenAI-compatible server. Models come from the
mlx-community organisation on the Hub, already converted and usually 4-bit.
pip install mlx-lm
mlx_lm.generate --model mlx-community/Mistral-7B-Instruct-v0.3-4bit --prompt "What is MLX?"
mlx_lm.server --model mlx-community/Mistral-7B-Instruct-v0.3-4bit # http://localhost:8080
--kv-bits 4 quantizes the KV cache for long contexts (and disables batching). The project’s own server
documentation says the MLX LM server is not recommended for production because it implements only basic
security checks — keep it on localhost, as a development convenience. See
mlx-lm and its
server documentation.
Comparison
| Runner | Platforms | API surface | Engine | GUI | Auth by default |
|---|---|---|---|---|---|
Ollama |
macOS, Linux, Windows, Docker |
native, OpenAI, Anthropic |
llama.cpp-derived |
app (basic) |
none (local server ignores keys) |
LM Studio |
macOS, Linux, Windows; headless |
native REST, OpenAI, Anthropic |
llama.cpp, MLX |
full |
off (opt-in API tokens) |
Jan |
macOS, Linux, Windows |
OpenAI |
llama.cpp |
full |
optional key |
Docker Model Runner |
Docker Desktop, Docker Engine |
OpenAI, Anthropic, Ollama |
llama.cpp, vLLM, Diffusers |
Docker Desktop |
none (documented as unauthenticated) |
LocalAI |
Docker, Linux, macOS |
OpenAI, Anthropic, more |
many backends |
web UI |
off unless |
MLX-LM |
macOS on Apple silicon |
OpenAI (basic) |
MLX |
none |
none (not for production) |
GPT4All, which some of the books in this sub-section’s bibliography use for local models with Q4_0 GGUFs, is now a niche
choice: the Hugging Face Hub integrates GGUF directly with llama.cpp, Ollama, LM Studio, Jan and Docker Model
Runner, and the runners above cover the same ground with current quant types (Q4_K_M) and OpenAI-compatible
servers. See GGUF on the Hugging Face Hub.
Client snippets
The clients are the same for every runner; change the base URL and model name from the table below.
| Runner | OpenAI base URL |
|---|---|
LM Studio |
|
Jan |
|
Docker Model Runner |
|
LocalAI |
|
MLX-LM |
curl (Docker Model Runner):
curl http://localhost:12434/engines/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "ai/smollm2",
"messages": [{"role": "user", "content": "What is retrieval-augmented generation?"}]}'
Python (LM Studio):
import os
from openai import OpenAI
from langchain_openai import ChatOpenAI
client = OpenAI(base_url="http://localhost:1234/v1", api_key=os.environ.get("LM_API_TOKEN", "lm-studio"))
reply = client.chat.completions.create(
model="openai/gpt-oss-20b",
messages=[{"role": "user", "content": "What is retrieval-augmented generation?"}],
)
print(reply.choices[0].message.content)
chat = ChatOpenAI(base_url="http://localhost:1234/v1", api_key=os.environ.get("LM_API_TOKEN", "lm-studio"),
model="openai/gpt-oss-20b")
Spring AI (MLX-LM, or any runner above) — the OpenAI starter:
spring:
ai:
openai:
base-url: http://localhost:8080/v1
api-key: not-needed
chat:
model: mlx-community/Mistral-7B-Instruct-v0.3-4bit
The base URL includes /v1 because Spring AI 2.0 appends /chat/completions to it as given; use the port of the
runner you started (LM Studio listens on 1234, for example). For Docker Model Runner the base URL is
spring.ai.openai.base-url=http://localhost:12434/engines/v1: the Spring AI reference also mentions
http://localhost:12434/engines, but on 2.0.1 that form misses the /v1 route (see
Spring AI: Local Models). The
ChatClient code is the same as on Ollama. See
Spring AI — OpenAI Chat,
LangChain — ChatOpenAI and
openai-python.