从零部署:Docker、模型与知识库实战)
当个人开发者或小团队想在自己的服务器上跑一套 AI 知识库、文档问答或私人助手服务时通常不会从训练模型开始也不会把所有数据直接交给云端 API。更务实的做法是在可控的环境里部署一套自托管方案把模型推理、向量检索、Web 管理界面全部串起来。本文要整理的这套配置方案代号就叫“伊洛伊Iloy”。简单来说它就是一套自托管 AI 问答服务的统称可以理解为你准备长期维护的那台“私人 AI 助手”。这类方案的好处非常明显数据留在自己的服务器上模型可以按需切换知识库内容完全由自己管理。但配置过程中也容易踩坑尤其是目录挂载、模型接入、环境变量不生效这几类问题网上资料零散不少还停留在旧版本。下面我会按实际部署顺序把环境准备、容器编排、模型接入、验证方法、常见报错和工程化建议完整过一遍保证照着操作能把一套服务跑起来。1. 背景与核心概念1.1 伊洛伊是什么从用户视角看伊洛伊是一个可以上传文档、建立知识库、然后通过对话问答获取答案的 Web 服务。你问它“上个月的项目文档里关于接口超时的结论是什么”它能先到知识库里做语义检索把最相关的片段捞出来再交给大模型组织成一段通顺的回答。从架构视角看这类服务一般由三部分组成首先是模型服务。它负责加载大语言模型接受提示词并生成回答。模型服务可以运行在本地也可以连接一个外部 API。本地部署的好处是数据不出服务器坏处是对硬件资源有要求。其次是向量检索与知识库。用户上传的文档会被切成片段经过嵌入模型转换成向量。查询时系统把用户的问题文本也转换成向量再通过相似度计算召回最相关的几个片段。这一步质量的好坏直接决定问答结果靠不靠谱。最后是 Web 服务与调度层。它负责提供管理界面、用户登录、知识库管理、API 路由等功能把模型服务和向量检索串成一个整体。伊洛伊在本文中主要指的就是这一层加上与之配套的容器编排。1.2 核心组件与分工我推荐的部署方式是容器化原因在于模型服务、Web 服务、数据库和前端资源依赖的运行时环境差异很大。把它们拆分成独立容器后版本升级和数据备份都可以单独处理。整个系统可以简化为下面几个角色组件职责示例Web 服务提供管理页面和 API伊洛伊主服务模型服务加载模型并生成回答Ollama嵌入模型把文本转换成向量bge-m3、text-embedding 系列向量存储保存和检索向量sqlite-vec、pgvector对象/文件存储保存上传的原始文档本地目录、MinIO这种拆分还有一个实际好处模型推理比较吃资源如果某个文档分析任务导致内存占用飙升受影响的是模型容器Web 服务不会直接崩溃重新拉起模型容器后其他服务依然在运行。1.3 容易混淆的几个概念在阅读各种配置文章时有几个概念经常被混着说大模型本身和部署模型的框架不是一回事。模型是参数文件比如一个 7B 参数的量化模型Ollama 只是一个加载和运行这些模型的工具负责把模型跑起来并提供 API。嵌入模型和对话模型不是同一个模型。对话模型负责生成文字嵌入模型负责把文本变成向量。很多知识库项目刚开始只配置了对话模型导致上传文档后检索结果为空就是因为还缺一个嵌入模型。Web 界面和底层服务不是同一个端口。登录页面、管理 API、模型 API 分别监听不同的端口配置防火墙时需要区分清楚。2. 环境准备与版本说明2.1 硬件与系统要求先明确一下建议的硬件门槛具体数值可以根据实际使用场景调整。CPU4 核起步。如果打算跑 7B 以内的量化模型CPU 推理勉强可用但响应速度会明显偏慢。内存至少 8GB。加载 7B 模型并运行知识库索引时16GB 会更从容。磁盘系统盘之外建议单独挂载一块数据盘容量预留 50GB 以上。模型文件、向量库、上传文档都会持续增长。GPU不是必须。有 Nvidia GPU 时推理速度会快很多没有 GPU 时可以先用 CPU 模式跑通流程后续再加显卡。操作系统Linux 最常见Ubuntu、Debian、CentOS 都可以。Windows 和 macOS 也能运行 Docker但生产环境建议使用 Linux 服务器。本文示例以 Ubuntu Server 系统为例重点演示配置思路。你在自己环境中操作时命令可能略有不同但原理一致。2.2 依赖软件需要提前装好以下软件Docker Engine 20.10 以上Docker Compose 插件docker compose命令Git用于拉取配置模板一个可用的 shell 环境能执行curl等基础命令检查 Docker 是否可用docker --version docker compose version如果这两条命令都能正常输出版本信息说明环境基础是好的。如果docker compose提示找不到可能需要单独安装 Compose 插件。2.3 项目目录设计在开始之前先规划好目录。目录规划的意义在于Docker 容器内部是临时的容器被删除后只有挂载出来的目录内容会保留。把数据放在宿主机固定目录里才能保证升级或迁移时不丢数据。推荐的项目根目录结构如下/home/user/iloy/ ├── docker-compose.yml ├── .env ├── .env.example ├── data/ │ ├── sqlite/ │ ├── uploads/ │ └── backups/ ├── models/ └── logs/其中data/存放知识库数据库、上传文档和备份文件models/用于缓存模型文件logs/保存各服务的日志。目录无需提前手动创建后面的命令脚本会自动生成但你需要知道每个目录的作用。这里有一个容易忽略的细节容器内运行的用户 ID 和宿主机当前用户的 UID 可能不一致。如果挂载目录后容器没有写权限日志会报permission denied。解决思路在后面的权限设计里会专门讲。3. 核心配置与原理拆解3.1 docker-compose.yml 的完整内容下面是一个最小可运行的docker-compose.yml示例。这里面的镜像地址、版本号都属于示例你需要替换成实际使用的项目和标签。文件路径/home/user/iloy/docker-compose.ymlversion: 3.8 services: iloy: image: ${ILOY_IMAGE:-registry.example.com/iloy/iloy:latest} container_name: iloy restart: unless-stopped ports: - ${ILOY_PORT:-8080}:8080 env_file: - .env environment: DATA_DIR: ${ILOY_DATA_DIR:-/data} DEFAULT_MODEL: ${DEFAULT_MODEL:-qwen2.5:7b} EMBEDDING_MODEL: ${EMBEDDING_MODEL:-bge-m3} VECTOR_DB: ${VECTOR_DB:-sqlite-vec} OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434} volumes: - ./data:/data - ./logs:/var/log/iloy depends_on: - ollama networks: - iloy-net ollama: image: ollama/ollama:latest container_name: ollama restart: unless-stopped volumes: - ./models:/root/.ollama networks: - iloy-net networks: iloy-net: driver: bridge这份配置的关键点有三个。第一iloy服务通过env_file加载.env文件同时environment里又显式指定了一些环境变量。后者的优先级在 Docker Compose 中高于前者适合固定一些默认值。不要同时用两种写法声明不同值否则排错时会很困惑。第二depends_on只保证容器启动顺序不保证服务已经可以对外提供服务。也就是说它只能让iloy在ollama容器启动之后再启动但如果模型还没加载完iloy仍然可能连不上 Ollama。这种情况需要主服务内部做重试或者手动等待模型加载完成。第三数据卷挂载用了相对路径./data和./models它们都相对于docker-compose.yml所在目录。如果你把这个文件放在别的位置路径也要跟着调整。3.2 环境变量文件 .env文件路径/home/user/iloy/.envILOY_PORT8080 ILOY_DATA_DIR/data DEFAULT_MODELqwen2.5:7b EMBEDDING_MODELbge-m3 VECTOR_DBsqlite-vec OLLAMA_ENDPOINThttp://ollama:11434 ILOY_API_KEY # 如果使用 OpenAI 兼容外部接口取消下一行注释并填写地址和密钥 # OPENAI_API_BASEhttps://api.example.com/v1 # OPENAI_API_KEYsk-xxxx.env文件里的变量会被 Docker Compose 自动读取用于替换docker-compose.yml中${VAR}格式的占位符。注意这里的变量不仅能传给容器内应用还能控制容器端口、镜像标签等编排层面的配置。所以它比一般应用配置的权限更大需要谨慎管理。ILOY_DATA_DIR/data是容器内的数据目录路径它和宿主机上的./data是两回事。不要混淆“宿主机路径”和“容器内路径”这是配置挂载时最常见的思维误区。OLLAMA_ENDPOINThttp://ollama:11434指向的是 Compose 网络内的服务名ollama而不是localhost。因为iloy容器和ollama容器是两个独立的网络命名空间不能直接访问对方的localhost。在 Compose 同一个网络里服务名可以当作主机名使用。3.3 数据卷与权限设计容器内进程通常以某种用户身份运行。如果镜像是基于ubuntu或debian的默认用户可能是root也可能是固定的非 root 用户。宿主机挂载目录的所有者和权限会直接影响容器内是否可写。最简单的做法是先创建目录然后手动把目录属主改成与容器内用户一致的 UID。mkdir -p data/sqlite data/uploads data/backups logs models id执行id后看到的 UID 是宿主机当前用户的。假设容器内的应用用户 UID 是 1000那你需要sudo chown -R 1000:1000 data logs models如果你的镜像用户也是 root那么不需要改但我建议尽量在容器配置里指定user字段避免以 root 身份运行服务。比如user: 1000:1000这就是最小权限原则在容器场景里的体现容器本身虽然是隔离的但挂载到宿主机的目录权限是共享的权限过大容易误删或覆盖宿主机的数据。3.4 模型接入的两种方式伊洛伊主服务本身不直接加载模型而是通过模型服务获取能力。常用的模型服务有两种。第一种是本地 Ollama。先在 Ollama 容器里拉取模型然后在主服务的环境变量里把OLLAMA_ENDPOINT指向 Ollama 地址即可。这种方式适合需要私有化、内网运行、不依赖外部接口的场景。第二种是 OpenAI 兼容的外部接口。如果你不想在本地跑模型可以直接配置OPENAI_API_BASE和OPENAI_API_KEY。这类接口返回格式一般是 OpenAI 风格主服务只要支持该协议就能对接。选择这种方式时需要注意 API Key 的保管和费用控制不能写进公开仓库还要确认接口所在网络是否可以从服务器访问。两种方式可以共存。实际项目里经常是“默认对话模型用本地某些超长文档处理走外部接口”具体看你的硬件条件和应用场景。4. 完整实战案例从零配置伊洛伊服务这一节我们从空目录开始一步步把服务跑起来。4.1 创建项目目录先建立项目根目录和子目录mkdir -p /home/user/iloy/{data,models,logs} cd /home/user/iloy接着创建.env.example作为配置模板保存方便以后再部署新机器时快速参考touch .env .env.example4.2 编写启动配置把上一节的docker-compose.yml和.env内容分别写入对应文件。这里建议把.env做成模板形式先复制一份cp .env.example .env然后将.env中ILOY_API_KEY那一行填入你自己的随机密钥。生成随机密钥可以用openssl rand -hex 32得到的字符串可以填入ILOY_API_KEY这个 Key 会作为访问服务 API 的凭证。4.3 校验并启动服务写好后先校验配置docker compose config这个命令会打印最终的完整配置同时检查 YAML 格式和变量替换是否正确。如果某个${VAR}引用了未定义的变量会在这里暴露出来。确认无误后启动docker compose up -d查看服务状态docker compose ps正常状态下iloy和ollama两个容器的状态都应该是Up。如果容器不断重启查看日志是最直接的排查方式docker compose logs -f iloy docker compose logs -f ollama4.4 拉取模型主服务启动后还需要让 Ollama 加载模型。在 Ollama 容器内执行拉取命令docker compose exec ollama ollama pull qwen2.5:7b模型较大根据网络情况需要等待一段时间。拉取完成后查看模型列表docker compose exec ollama ollama list看到刚才拉取的模型出现在列表里说明模型服务已经准备好。如果拉取速度很慢可以检查网络环境和镜像加速配置但注意不要为了这一点去配置不合规的代理工具。同时嵌入模型也要准备好。不同的知识库项目对接嵌入模型的方式略有不同有的是通过 Ollama 加载有的需要单独调用嵌入接口。请参考你的伊洛伊主服务版本说明在管理界面或配置文件中把EMBEDDING_MODEL指定为实际可用的嵌入模型名称。4.5 验证服务健康状态等服务启动完成用 curl 检查健康接口curl http://localhost:8080/api/v1/health正常时返回类似下面的 JSON具体字段以你实际部署版本为准{status:ok,vector_store:sqlite-vec}如果返回的是连接拒绝或超时先确认端口映射是否正确docker compose ps再看iloy容器内部是否能访问 Ollamadocker compose exec iloy curl http://ollama:11434这一步能快速定位是“主服务没起来”还是“网络不通”。注意某些镜像里可能没有curl此时可以用wget或进入容器后用 Python 的urllib请求但最稳妥的做法是在宿主机先确认两个容器都在运行状态。4.6 首次创建知识库服务健康检查通过后打开浏览器访问http://服务器IP:8080进入管理界面。首次使用大致需要三步创建管理员账号并登录。在“知识库”页面新建一个知识库命名能反映内容范围比如“产品文档库”。上传文档。支持常见文本格式比如 PDF、Markdown、TXT。上传后系统会先切片再调用嵌入模型生成向量。上传完成后知识库状态里会显示已索引的文档数量和片段数量。这一步没有报错不代表所有内容都正确建议抽样检查几个问题验证检索结果是否和上传内容相关。5. 常见问题与排查思路5.1 问题速查表问题现象常见原因解决思路docker compose up报网络相关错误Compose 网络配置异常或手动删过网络检查networks定义必要时用docker network prune清理容器启动后立即退出端口被占用、环境变量缺失、镜像启动命令有问题查看docker compose logs逐一核对环境变量日志报permission denied挂载目录属主与容器内用户 UID 不一致检查镜像内用户 UID用chown调整宿主机目录属主页面能打开但问答超时模型未拉取完成或 Ollama 不可用进入 Ollama 容器执行ollama list手动拉取模型上传文档后检索结果为空嵌入模型未生效或知识库索引失败检查EMBEDDING_MODEL重建知识库索引修改.env后配置不生效Compose 没有重建容器使用docker compose up -d --force-recreate重新创建API 请求返回 401ILOY_API_KEY未设置或调用时未携带检查.env中 Key重新创建容器再验证5.2 典型问题详细排查第一个高发问题是“修改.env后不生效”。很多人改完.env后只执行docker compose restart但容器启动时读入的环境变量是首次创建时写入的restart不会重新读取环境变量。正确做法是docker compose up -d --force-recreate如果.env中的变量发生较大变化比如改变了ILOY_PORT那么端口映射也会变化。这时只重建容器还不够可能还要重新创建网络或调整防火墙规则。第二个高发问题是“模型加载太慢或内存不足”。默认情况下 Ollama 启动时会加载模型到内存如果你的服务器内存只有 8GB加载 7B 模型很容易导致内存耗尽。可以改用更小的量化版本比如qwen2.5:3b或qwen2.5:1.5b。也可以设置 Ollama 的并发参数限制同时处理的推理任务数量避免多个请求同时打满内存。第三个高发问题是“上传文档中文乱码”。这通常不是代码问题而是文档编码不是 UTF-8。旧版 Word 或部分 PDF 可能只包含 GBK 编码的文本。处理方法是先统一转换文档格式优先保存为 UTF-8 的 Markdown 或 TXT再上传到知识库。如果你的文档质量要求较高这一步值得投入时间。6. 最佳实践与工程建议6.1 数据安全与备份策略数据是最容易丢失的资产。容器可以随时重建但知识库数据一旦丢了重新构建索引的成本非常高。建议至少做到以下几点把data/、models/挂载到独立数据盘不要放在系统盘根目录。定期备份整个data/目录。备份前先停止写入或使用数据库的一致性备份工具。如果向量库基于 SQLite备份时优先使用 SQLite 自身的.backup命令而不是直接复制数据库文件直接复制可能在热备份时生成不一致的副本。备份文件存放在另一台机器或对象存储不要和原数据放在同一块磁盘上。影响范围分析如果只备份了docker-compose.yml而没有备份数据目录迁移到新机器后服务能启动但知识库会变成空库。同样地如果目录挂载错误比如把./data挂到了容器内的/tmp容器重启数据就会丢失。这个问题在容器升级时尤其危险一定要在升级前确认docker volume或宿主机目录路径没有被改变。6.2 密钥管理与最小权限ILOY_API_KEY、外部接口的API_KEY、管理员密码都属于敏感信息。实际项目中要遵守最小权限原则.env文件不要提交到 Git 仓库。先在.gitignore中加入.env只提交.env.example。外部接口的 Key 建议通过环境变量或密钥管理服务注入不要硬编码到镜像或启动命令中。容器内的进程尽量使用非 root 用户运行通过 Compose 的user字段指定。端口不要在大范围开放。如果只是内网使用监听127.0.0.1即可如果需要外部访问建议在前面加 Nginx 反向代理并启用 HTTPS。其中“端口监听范围”的影响范围很大。0.0.0.0表示所有网卡都监听只要服务器有公网 IP外部请求就能直达管理接口。如果接口没有强认证任何人都可能上传文档或读取知识库。这个问题的风险远大于容器本身的漏洞风险。6.3 升级与回滚流程自托管服务升级时不要直接在原环境上执行最新镜像。推荐的升级路径在测试环境或当前环境的副本上先跑新版本。备份data/和当前使用的镜像标签。修改docker-compose.yml中的镜像标签执行docker compose pull。执行docker compose up -d --force-recreate重建服务。观察日志和健康检查接口确认知识库能正常检索后再继续使用。如果升级后发现问题可以快速回滚到旧镜像标签把镜像标签改回原版本再执行一次docker compose up -d --force-recreate。回滚通常能恢复服务但如果新版本启动时已经修改过数据库 schema旧版本可能无法读取新格式的数据。所以升级前的备份不是可选项而是必选项。6.4 性能与资源控制资源限制不能靠“到时候再说”。建议在 Compose 中为容器设置内存和日志限制。比如services: ollama: deploy: resources: limits: memory: 8G日志文件如果不限制长期运行后可能占满磁盘。在 Compose 中开启日志轮转logging: driver: json-file options: max-size: 50m max-file: 3这类配置影响的是长期稳定性不是一天两天能看出来的。很多服务运行几个月后突然磁盘满就是因为日志或模型缓存文件没人清理。7. 总结与下一步到这里一套完整的伊洛伊配置已经跑通了从目录规划、docker-compose 编排、模型接入到健康检查、知识库创建和常见问题排查覆盖了一个自托管 AI 问答服务的主要生命周期。读完这篇文章你至少应该能回答这几个问题数据和代码目录为什么分开.env修改后为什么不生效模型服务和知识库服务的关系是什么以及遇到权限报错时先查什么。下一步可以继续深入的方向包括接入更多开源模型、优化向量检索的切片策略、给服务增加统一登录认证、把数据迁移到 PostgreSQL 向量库以及设计更完善的备份机制。每个方向都是一篇可以单独展开的话题。在实际项目中优先关注数据安全和配置变更的可回滚性其次才是功能和性能。服务跑起来不难难的是把它长期稳定地维护下去。希望这份配置笔记能帮你少走一些弯路动手在自己的服务器上试一遍遇到问题也能按上面的思路逐步定位。