背景

在单机环境中使用 Docker Compose 同时运行 Ollama(推理核心)与 Open-WebUI(交互界面)是一个常见做法。初期将两者放在同一个 compose 文件中管理较为方便,但随着系统逐步演进,这种方式会带来以下问题:

  • 服务职责耦合,后期维护和扩展不清晰
  • 难以引入新的 AI 服务(如工作流、代理、API 网关)
  • 对数据与网络边界的控制不够明确

因此,本次实践的目标是:

在不丢失任何既有数据的前提下,将 Ollama 与 Open-WebUI 拆分为两个独立的 Docker Compose 栈,并通过一个核心网络进行连接。


设计原则

  1. 核心明确:Ollama 作为推理核心,其他服务围绕其运行
  2. 网络唯一:仅存在一个核心网络,用于 AI 内部通信
  3. 数据不可丢失:所有历史模型与 WebUI 数据必须完整保留
  4. 职责分离:每个栈只负责自己的服务,不跨栈依赖启动顺序
  5. Compose 原生:尽量使用 Compose 行为,不依赖手工创建资源

原始状态分析

在最初的一体化部署中,Docker 会自动为服务创建数据卷。拆分为多个栈后,如果未显式指定旧数据卷名称,Docker 会为新栈创建新的空卷,从而导致“历史数据消失”的错觉。

需要明确的是:

数据通常并未被删除,而是新容器未挂载到正确的旧数据卷。

因此,拆分过程中最关键的工作并不是服务启动顺序,而是数据卷的精确绑定


拆分思路概述

整体架构被拆分为两类栈:

  • 核心栈:仅包含推理服务,负责创建并维护核心内部网络
  • 外围栈:包含界面或其他 AI 服务,通过外部方式接入核心网络

所有服务间通信均通过 Docker 内部网络完成,核心服务不直接对外暴露。


核心栈配置要点(示意)

核心栈的职责包括:

  • 运行推理服务
  • 挂载既有模型数据卷
  • 创建并命名核心内部网络

配置示意(省略非关键字段):

services:
  core-service:
    image: <image>
    volumes:
      - core-data:/data
    networks:
      - core-net

volumes:
  core-data:
    external: true

networks:
  core-net:
    name: core-net

外围栈配置要点(示意)

外围栈的职责包括:

  • 提供用户界面或辅助能力
  • 精确挂载原有数据卷
  • 通过外部方式使用核心网络

配置示意(省略非关键字段):

services:
  ui-service:
    image: <image>
    volumes:
      - ui-data:/data
    networks:
      - core-net

volumes:
  ui-data:
    external: true

networks:
  core-net:
    external: true

常见问题与判断方法

为什么拆分后看起来“数据没了”?

  • Compose 项目名称变化
  • 数据卷名称未显式指定
  • Docker 默认行为是“新建而非猜测”

如何判断旧数据卷是否仍然存在?

可通过 Docker 的卷列表与磁盘占用信息进行判断。通常:

  • 旧数据卷体积明显更大
  • 新创建的数据卷体积接近为空

最终状态

在完成拆分与修正后,系统应达到以下状态:

  • 核心服务与界面服务完全解耦
  • 核心网络只创建一次并长期复用
  • 所有历史数据完整保留
  • 架构可自然扩展新的 AI 服务栈

总结

本次实践的核心经验可以归纳为一句话:

Docker 不会替用户判断应当使用哪个历史数据卷,所有关键数据都必须显式绑定。

通过明确:

  • 核心网络的唯一性
  • 核心数据卷的归属
  • 外围服务对核心资源的外部依赖关系

可以在不增加额外心智负担的前提下,构建一个长期稳定、可维护、可扩展的个人 AI 基础设施。

Leave a Reply

Your email address will not be published. Required fields are marked *