# 从 Ollama 迁移到 Docker Model Runner：一份可验证、可回退的实操教程

> **目标**：在不夸大性能收益的前提下，把一个本地 LLM 从 Ollama 迁移到 Docker Model Runner（DMR），验证 CLI、兼容 API 与 Docker Compose 模型依赖，并保留清晰的回退路径。
>
> **适用对象**：已经使用 Docker Desktop 或 Docker Engine 管理应用，希望把模型版本、配置和应用生命周期放进同一套工作流的开发者。
>
> **不适用对象**：只在单机偶尔聊天、没有 Docker 工作流，或期待“换成 Docker 后推理一定更快”的用户。原文没有提供性能基准，DMR 的主要价值是管理、分发与 Compose 集成。

## 1. 你将完成什么

完成后，你会得到三个可独立验证的闭环：

1. 使用 `docker model` 拉取并运行一个小模型；
2. 通过 Ollama 兼容 API 和 OpenAI 兼容 API 调用同一个模型；
3. 在 `compose.yaml` 中把模型声明为应用依赖，而不是写死一个外部 Ollama 地址。

迁移路线：

```text
现有应用 ── Ollama API :11434
    │
    ├─ 记录旧配置与模型
    ├─ 启用 Docker Model Runner
    ├─ 重新拉取模型制品
    ├─ 先验证 DMR :12434
    └─ 只修改应用 base URL ──> Docker Model Runner
                                  ├─ llama.cpp / GGUF
                                  ├─ vLLM / Safetensors（硬件受限）
                                  └─ Diffusers（硬件受限）
```

## 2. 事实、提炼与实践扩展

### 原文事实

- DMR 可以把 GGUF 或 Safetensors 模型当作带标签的制品管理，并推送到兼容 OCI 的注册表。
- DMR 支持 OpenAI 与 Ollama 兼容 API；原文迁移时主要修改了 API 地址。
- Ollama 默认使用 `11434`，DMR 的主机 TCP 端口通常为 `12434`。
- DMR 可根据模型格式使用不同推理引擎；作者实际验证的是 Mac 上量化 GGUF、llama.cpp 与 Metal。
- Ollama 与 DMR 分别保存模型，迁移通常需要重新下载。
- 作者没有证明 DMR 性能更好，结论只是 Docker 工作流下更方便。

### 本文提炼

真正值得迁移的不是“运行一条不同的命令”，而是把模型变成项目依赖：模型标识、上下文配置、应用绑定和启动方式可以一起审查、复现与交接。

### 实践扩展

本文增加了原文没有展开的预检、双轨验证、Compose 最小示例、失败处理与回退步骤。它们依据 Docker 官方文档整理，不代表原作者亲自执行过这些步骤。

## 3. 前置条件与边界

### 3.1 软件要求

- Docker Desktop，或已安装 Docker Engine 的 Linux；
- 若使用 Compose 模型：Docker Compose **v2.38 或更高版本**；
- `curl`；
- 足够的磁盘空间和内存；首次拉取模型可能耗时较长。

先检查：

```bash
docker version
docker compose version
docker model version
```

**通过标准**：三条命令均正常返回版本信息，且 Compose 版本不低于 v2.38。

**失败处理**：

- 若出现 `docker: 'model' is not a docker command`，说明 Model Runner 尚未启用或插件未安装；
- 若 Compose 低于 v2.38，不要继续测试 `models:`，先升级 Compose；
- 不要为了“试一下”删除现有 Ollama 模型。

### 3.2 硬件边界

- GGUF 通常走 llama.cpp；Apple Silicon 可使用 Metal 加速；
- vLLM 面向更高吞吐服务，但需要匹配的 NVIDIA GPU 与受支持系统；
- Diffusers 也有 NVIDIA/CUDA 与平台限制；
- 模型能否加载取决于模型大小、量化方式、上下文长度和可用内存，而不是仅看参数量。

本教程选择体积较小的 `ai/smollm2:360M-Q4_K_M` 作为验证模型，目的是先证明链路，不代表它适合真实业务。

## 4. 步骤一：记录 Ollama 基线并准备回退

迁移前先记录现状：

```bash
mkdir -p dmr-migration-evidence
ollama list | tee dmr-migration-evidence/ollama-models.txt
curl -fsS http://localhost:11434/api/tags \
  | tee dmr-migration-evidence/ollama-tags.json
```

再记录应用中的旧配置，例如：

```text
OLLAMA_BASE_URL=http://localhost:11434
MODEL_NAME=<当前模型名>
```

**验证标准**：

- `ollama-models.txt` 中能看到当前模型；
- `ollama-tags.json` 是非空 JSON；
- 已知道应用在哪里配置 base URL 和模型名。

如果 Ollama 本身不可用，先停止迁移。否则后面出现问题时，你无法判断是原环境故障还是迁移引入的问题。

## 5. 步骤二：启用 Docker Model Runner

### 5.1 Docker Desktop

进入 **Settings → AI**：

1. 启用 **Docker Model Runner**；
2. 若要从主机进程调用 API，启用 **host-side TCP support**；
3. 端口使用 `12434`；
4. Windows 且有受支持 NVIDIA GPU 时，再按需要启用 GPU 推理。

然后检查：

```bash
docker model version
docker model status
```

### 5.2 Linux Docker Engine

Ubuntu/Debian：

```bash
sudo apt-get update
sudo apt-get install docker-model-plugin
```

RPM 系发行版：

```bash
sudo dnf update
sudo dnf install docker-model-plugin
```

再验证：

```bash
docker model version
```

Docker Engine 的 TCP 支持默认使用 `12434`。如果你修改了端口，后续命令必须同步修改。

## 6. 步骤三：拉取并运行验证模型

拉取固定量化标签，避免不同机器隐式选择不同制品：

```bash
docker model pull ai/smollm2:360M-Q4_K_M
docker model list
```

运行一次 CLI 对话：

```bash
docker model run ai/smollm2:360M-Q4_K_M \
  "只回答一个词：Docker"
```

**预期输出形态**：命令返回模型生成的文本，内容应包含 `Docker`。具体格式和生成速度会随版本、硬件与模型而变化。

**失败处理**：

```bash
docker model logs
```

重点检查：

- 模型是否完整下载；
- 是否内存不足；
- 模型标签是否拼写正确；
- Model Runner 是否处于运行状态。

> 不要在首次拉取很慢时直接判断 DMR 性能差。下载耗时和推理耗时是两件事。

## 7. 步骤四：验证两套兼容 API

### 7.1 先检查模型列表

Ollama 兼容端点：

```bash
curl -fsS http://localhost:12434/api/tags
```

OpenAI 兼容端点：

```bash
curl -fsS http://localhost:12434/engines/v1/models
```

**通过标准**：响应为非空 JSON，并包含 `ai/smollm2` 或所拉取的完整标签。

如果主机访问失败：

- Docker Desktop：确认已启用 host-side TCP support；
- 检查 `12434` 是否被其他进程占用；
- 确认没有把容器内地址和主机地址混用。

### 7.2 Ollama 兼容调用

```bash
curl -fsS http://localhost:12434/api/chat \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "ai/smollm2:360M-Q4_K_M",
    "stream": false,
    "messages": [
      {"role": "user", "content": "用一句中文解释什么是容器镜像。"}
    ]
  }'
```

### 7.3 OpenAI 兼容调用

```bash
curl -fsS http://localhost:12434/engines/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "ai/smollm2:360M-Q4_K_M",
    "temperature": 0,
    "messages": [
      {"role": "user", "content": "只返回 JSON：{\"status\":\"ok\"}"}
    ]
  }'
```

**验证标准**：

- HTTP 请求成功；
- 响应是 JSON；
- Ollama 兼容响应包含模型回复字段；
- OpenAI 兼容响应包含 `choices`；
- 不要只看“答案像真的”，还要确认请求实际发到了 `12434`。

DMR 本地 OpenAI 兼容 API 不要求真实 API key。若第三方客户端强制填写，可用无意义占位值，例如 `not-needed`，不要放入真实云端密钥。

## 8. 步骤五：把现有客户端从 Ollama 切到 DMR

最小迁移原则：先只改端点，不同时更换模型、提示词和采样参数。

| 客户端类型 | Ollama 旧地址 | DMR 新地址 |
|---|---|---|
| Ollama 兼容客户端 | `http://localhost:11434` | `http://localhost:12434` |
| OpenAI 兼容客户端 | 视原配置而定 | `http://localhost:12434/engines/v1` |

示例环境变量：

```bash
export MODEL_BASE_URL=http://localhost:12434/engines/v1
export MODEL_NAME=ai/smollm2:360M-Q4_K_M
export MODEL_API_KEY=not-needed
```

切换后，用同一条固定提示分别请求 Ollama 和 DMR，并记录：

- 是否成功；
- 首次响应时间；
- 总响应时间；
- 内存/显存占用；
- 输出是否满足任务要求。

原文没有提供这些基准，所以不要把个人体验当成你的验收结论。

## 9. 步骤六：用 Docker Compose 声明模型依赖

下面的例子展示 Compose 模型绑定。它不会部署生产应用，只用容器打印自动注入的模型地址和模型名。

**文件：`compose.yaml`**

```yaml
services:
  model-env-check:
    image: alpine:3.22
    command:
      - /bin/sh
      - -ec
      - |
        test -n "$$LLM_URL"
        test -n "$$LLM_MODEL"
        printf 'LLM_URL=%s\n' "$$LLM_URL"
        printf 'LLM_MODEL=%s\n' "$$LLM_MODEL"
    models:
      - llm

models:
  llm:
    model: ai/smollm2:360M-Q4_K_M
    context_size: 2048
```

先做静态解析：

```bash
docker compose config
```

再运行：

```bash
docker compose up --abort-on-container-exit
```

**预期输出形态**：

```text
LLM_URL=<由平台注入的非空模型端点>
LLM_MODEL=ai/smollm2:360M-Q4_K_M
```

这里使用 `$$LLM_URL` 而不是 `$LLM_URL`，是为了让变量在容器内展开，而不是被 Compose 提前替换。

**通过标准**：

- `docker compose config` 无语法错误；
- 服务退出码为 0；
- 两个变量均非空；
- 模型名与 `compose.yaml` 中声明的一致。

**失败处理**：

- `models` 字段不识别：检查 Compose 是否达到 v2.38；
- 模型未拉取：检查 Model Runner 状态和注册表访问；
- 变量为空：确认服务的 `models` 绑定与顶层模型名称一致；
- 不要用硬编码 `localhost:12434` 替代注入变量来“绕过”失败，否则会丢失 Compose 模型依赖的核心价值。

## 10. 可选：自定义变量名

如果应用已经使用 `AI_MODEL_URL` 和 `AI_MODEL_NAME`，可以使用长语法：

**文件：`compose.yaml`（替换服务中的绑定部分）**

```yaml
services:
  app:
    image: your-app-image
    models:
      llm:
        endpoint_var: AI_MODEL_URL
        model_var: AI_MODEL_NAME

models:
  llm:
    model: ai/smollm2:360M-Q4_K_M
    context_size: 2048
```

这是配置契约示例；`your-app-image` 必须替换为真实应用镜像，应用也必须读取这两个环境变量。

## 11. 迁移验收清单

只有以下条件全部满足，才建议停用旧 Ollama 路径：

- [ ] 已保存 Ollama 模型列表和旧端点配置；
- [ ] `docker model version` 与 `docker model status` 正常；
- [ ] DMR 已拉取明确标签的模型；
- [ ] CLI 调用成功；
- [ ] `12434/api/tags` 或 OpenAI 模型列表端点成功；
- [ ] 真实客户端只修改 base URL 后仍能工作；
- [ ] Compose 配置能解析，模型变量能注入；
- [ ] 已记录下载成本、内存/显存和响应表现；
- [ ] 回退到 Ollama 的配置仍可用；
- [ ] 没有把“更方便”写成“性能更好”。

## 12. 回退步骤

若 DMR 链路不稳定，先恢复应用端点：

```bash
export MODEL_BASE_URL=http://localhost:11434
export MODEL_NAME=<原 Ollama 模型名>
```

然后验证：

```bash
curl -fsS http://localhost:11434/api/tags
```

如果应用恢复正常，说明回退完成。此时保留 DMR 模型用于排查，不要急着删除任何一侧的模型文件。

只有确认不再需要测试模型时，才执行清理：

```bash
docker model rm ai/smollm2:360M-Q4_K_M
```

## 13. 常见误区

### “换成 Docker，推理一定更快”

不成立。原文没有性能测试；DMR 的主要收益是模型制品化、Compose 集成与统一管理。

### “Ollama 的模型文件可以直接复用”

原文作者需要重新下载。迁移前应预留网络时间和磁盘空间。

### “端口改成 12434 就算迁移完成”

不够。还应验证模型标识、API 格式、真实客户端、Compose 绑定和回退路径。

### “本地 API 不需要密钥，所以可以对公网开放”

危险。DMR 本地兼容 API不要求真实 API key，不等于适合裸露在公网。需要远程访问时，应增加网络隔离、认证、TLS 和访问控制；这些属于部署扩展，不是原文已验证结论。

### “上下文越大越好”

上下文长度会消耗更多内存。官方建议根据实际任务保持尽可能小的上下文；先用 `2048` 做链路验证，再按需求调整。

## 14. 原文方法覆盖清单

| 原文方法 | 是否纳入 | 落点 | 若遗漏原因 |
|---|---:|---|---|
| 启用 Docker Model Runner | 是 | 步骤二 | — |
| 使用 `docker model pull` 与 `docker model run` | 是 | 步骤三 | — |
| 将 API 从 Ollama 端口切换到 DMR | 是 | 步骤四、五 | — |
| 使用 OpenAI/Ollama 兼容 API | 是 | 步骤四 | — |
| 在 Compose 中声明模型依赖 | 是 | 步骤六、十 | — |
| 将模型作为带版本的 OCI 制品管理 | 部分 | 步骤三及原理说明 | 推送私有注册表涉及账号和外部写入，不纳入最小迁移闭环 |
| 使用 GGUF、Safetensors 与不同推理引擎 | 部分 | 硬件边界 | 原文未实际验证 vLLM 与 Diffusers，本教程不伪造运行结果 |
| 重新下载模型 | 是 | 步骤一、三及验收清单 | — |
| 判断是否值得从 Ollama 迁移 | 是 | 适用对象、验收清单、误区 | — |

## 15. 验证证据级别

- `static_publish_ok`：教程的 Markdown/HTML 生成、结构与发布检查通过后方可标记为真；
- `mock_code_run_ok`：不适用，本教程没有用 Mock 替代模型；
- `real_backend_contract_ok`：本文命令依据 Docker 官方文档整理，但应在读者自己的 DMR 版本上重新验证；
- `real_backend_smoke_ok`：本文不声称已在你的硬件上完成模型拉取和推理。只有你实际执行步骤三至六并保存输出后，才能标记为真。

## 参考资料

- 原文：[I ditched Ollama for Docker, and my local LLM setup finally stopped being a hassle](https://www.xda-developers.com/ditched-ollama-docker-containers-local-llm-setup-stopped-being-hassle/)，XDA Developers，Anurag Singh，发布于 2026-07-28。
- Docker 官方：[Docker Model Runner 入门](https://docs.docker.com/ai/model-runner/get-started/)
- Docker 官方：[在 Compose 中定义 AI 模型](https://docs.docker.com/ai/compose/models-and-compose/)
- Docker 官方：[Docker Model Runner REST API](https://docs.docker.com/ai/model-runner/api-reference/)

> 文档命令与接口已按 2026 年 7 月可访问的 Docker 官方文档核对。Docker Model Runner 仍可能迭代；正式迁移前，请再次检查与你安装版本对应的官方文档。