1. 为什么要在局域网里自建 AI Agent 平台
1.1 从“调用云端 API”到“把模型搬回自己机房”的转变
过去一年,我身边不少做企业内部工具的朋友都经历过同一个心路历程:一开始图省事,直接调用公有云的大模型接口,几行代码就能跑通对话。但真到了要落地到业务场景里,问题就一个接一个冒出来——数据合规部门盯着你问“用户输入的内容到底去了哪里”,网络部门告诉你生产环境根本不通外网,财务那边看着按 token 计费的账单直皱眉。尤其是当你想把 AI Agent 接入内部知识库、工单系统、代码仓库这些敏感数据源时,把数据往外发这件事本身就很难过审。
于是“局域网自建”这条路就被越来越多团队重新捡了起来。所谓局域网自建,说白了就是把模型推理服务、Agent 编排框架、向量库这些组件全部部署在自己能掌控的内网机器上,外部网络完全隔离,数据不出机房。这样做的好处很直接:数据流向完全可控,没有按量计费的焦虑,延迟也稳定得多——毕竟走的是内网千兆甚至万兆,比绕一圈公网快得多。
但自建也有自建的坑。模型权重动辄几十 GB,推理框架依赖一堆 CUDA 版本,Agent 框架又要 Python 环境、又要各种系统库,装完一台机器往往就“污染”了整台服务器的环境。这时候容器化就成了几乎唯一优雅的解法,而 Docker 又是其中门槛最低、生态最成熟的选择。标题里提到的“Docker 部署 DeepSeek Harness”,本质上就是把这套“模型 + Agent 编排 + 内网服务”的组合拳,用一个可复现的容器方案打包起来,让运维和开发都能省心。
1.2 这套方案到底解决了谁的痛点
我把这套方案的价值拆成三类人来理解,这样你对号入座会更清楚。
第一类是企业内部的技术团队。他们需要给业务部门提供一个“能问内部文档、能查内部数据”的智能助手,但又不允许数据外流。局域网部署 + Docker 化,意味着他们可以在一台内网服务器上把整套东西跑起来,业务同事通过浏览器访问内网地址就能用,IT 部门也不用担心合规问题。
第二类是对成本敏感的独立开发者或小团队。公有云 API 用起来爽,但量一大账单就吓人。如果手上正好有一台带显卡的机器(哪怕是消费级的),把它利用起来跑本地模型,长期看成本低得多。Docker 方案让环境搭建从“折腾两天”压缩到“半小时搞定”。
第三类是做技术预研和 Demo 的人。他们需要在没有外网、或者网络受限的环境里快速搭一个能演示的 AI Agent,验证产品思路。容器化方案最大的好处就是“一次打包,到处运行”,换台机器照样能起来,不用重新配环境。
需要说明的是,标题里的“DeepSeek Harness”我理解为一种把 DeepSeek 系列模型与 Agent 编排能力封装在一起的运行框架或脚手架。由于原始描述比较零散,下面涉及的具体组件选型、参数配置,我会基于“一个合格从业者在局域网自建 AI Agent 平台时最可能采用的合理方案”来补全,并在关键处标注这是常见实践而非唯一答案。
1.3 阅读前你需要具备的基础
这篇内容不是“点一下就能用”的傻瓜教程,它更适合有一定 Linux 和 Docker 基础的人。你至少需要能看懂docker run命令、知道什么是端口映射、能通过 SSH 登录服务器。如果你还会一点 Python 和 YAML 配置,那读起来会更顺。
如果你是完全的新手,我建议先补两个基础:一是 Docker 的基本概念(镜像、容器、卷、网络),二是大模型推理的基本流程(模型加载、显存占用、量化)。这两块搞明白了,后面的内容就不会觉得云里雾里。
2. 整体架构设计与组件选型思路
2.1 一套典型的局域网 AI Agent 平台长什么样
在动手之前,先把架构想清楚,比上来就敲命令重要得多。我见过太多人一上来就docker run,结果跑起来发现端口冲突、显存不够、模型加载失败,回头返工的时间比规划的时间还长。
一套完整的局域网 AI Agent 平台,通常包含这么几层:
- 模型推理层:负责加载 DeepSeek 模型权重,对外提供推理接口。常见做法是用推理引擎(如 vLLM、TGI 或 Ollama)把模型跑起来,暴露一个兼容 OpenAI 格式的 HTTP 接口。
- Agent 编排层:也就是标题里的 Harness,负责把用户输入、工具调用、记忆管理、提示词模板这些东西串起来。它通常是一个 Python 服务,通过 HTTP 调用推理层。
- 数据与工具层:向量数据库(存知识库 embedding)、外部工具接口(查数据库、调内部 API)等。
- 接入层:一个 Web UI 或者 API 网关,让用户能访问。局域网场景下,通常就是一个内网 IP + 端口。
这四层用 Docker 网络串起来,推理层和编排层可以放在同一台机器,也可以分开放。如果只有一台带显卡的服务器,全部塞一起也行,但要注意显存和内存的分配。
2.2 为什么选 Docker 而不是裸机或虚拟机
这个问题我被问过很多次,这里把我的思考过程完整说一下。
裸机部署的问题是环境不可复现。你今天在这台机器上装好了 CUDA 12.1 + PyTorch 2.1 + 某个特定版本的推理框架,明天换台机器,驱动版本不一样,可能就跑不起来。而且 Python 的依赖冲突是出了名的,Agent 框架要 A 版本的库,推理框架要 B 版本,装在一起就打架。
虚拟机的优势是隔离彻底,但代价是资源开销大。GPU 直通到虚拟机里配置起来也麻烦,性能还有损耗。对于一台本来就不算富裕的内网服务器来说,虚拟机有点重。
Docker 恰好卡在中间:隔离性够用(进程、网络、文件系统都隔离),资源开销小(共享内核),而且 GPU 支持已经相当成熟(NVIDIA Container Toolkit)。最关键的是,Dockerfile 和 docker-compose.yml 本身就是文档,别人拿到你的配置,能一比一复现出同样的环境。这对团队协作和后续维护太重要了。
提示:如果你的服务器有多张显卡,Docker 可以通过
--gpus参数精确指定用哪几张,这在多模型共存或者多人共用一台机器时非常有用。
2.3 推理引擎选型:vLLM、TGI 还是 Ollama
这是整个方案里最关键的一个决策,直接影响性能和易用性。我把三个主流选项拉出来对比一下。
| 引擎 | 优势 | 劣势 | 适合场景 |
|---|---|---|---|
| vLLM | 吞吐量高,PagedAttention 显存利用率好,支持并发 | 配置相对复杂,对显存要求较高 | 多人并发访问的生产环境 |
| TGI | 官方维护,部署规范,支持多种量化 | 资源占用偏大,定制化不如 vLLM 灵活 | 追求稳定、标准化的团队 |
| Ollama | 上手极简,模型管理方便,自带量化 | 并发能力弱,不适合高负载 | 个人开发、小团队、Demo 验证 |
我的建议是:如果是个人或者三五人的小团队,先用 Ollama 把流程跑通,它的ollama pull和ollama run体验非常顺滑,几分钟就能看到模型说话。等验证完产品思路,需要上生产、要扛并发的时候,再换成 vLLM。这个“先跑通再优化”的路径,比一上来就啃 vLLM 的配置要省心得多。
需要提醒的是,DeepSeek 系列模型有不同的参数规模,选哪个版本要看你手上的显卡显存。粗略的估算方法是:FP16 精度下,每 10 亿参数大约需要 2GB 显存,再加上 KV Cache 和框架开销。比如一个 7B 的模型,FP16 大概要 14GB 显存,加上缓存和开销,16GB 显存的卡勉强够用;如果做 4-bit 量化,显存需求能降到三分之一左右。这个计算过程你在选型时一定要自己过一遍,别等下载完几十 GB 权重才发现跑不起来。
2.4 网络与端口规划:局域网部署的隐形坑
局域网部署听起来简单,但端口规划没做好,后面全是麻烦。我踩过的坑包括:推理服务端口和编排服务端口撞了、Docker 内部网络和宿主机网络混在一起导致容器互相访问不到、防火墙规则没放行导致内网其他机器访问不了。
我的做法是提前画一张端口分配表,把每个服务的端口固定下来,写进 compose 文件里。比如推理服务用 8000,编排服务用 8080,Web UI 用 3000,向量库用 6333。这样既避免了冲突,也方便排查问题——看到某个端口,就知道是哪个服务。
另外,Docker 的网络模式建议用自定义 bridge 网络,而不是默认的 bridge。自定义网络里,容器之间可以用服务名互相访问,不用记 IP,配置起来清爽很多。如果确实需要让内网其他机器访问,就把端口映射到宿主机,然后确认宿主机的防火墙放行了这些端口。
3. 核心组件拆解与实操要点
3.1 模型推理服务的容器化配置
先把推理层搞定,这是整个平台的地基。以 Ollama 为例,它的容器化部署相对简单,但有几个细节不注意就会翻车。
首先是 GPU 支持。你得先装好 NVIDIA 驱动和 NVIDIA Container Toolkit,然后在docker run时加上--gpus all或者指定具体显卡。如果这一步没做对,容器里是看不到 GPU 的,模型会退回到 CPU 推理,速度慢到无法接受。
其次是模型存储。模型权重动辄几十 GB,绝对不能放在容器内部——容器一删,权重就没了,下次还得重新下载。正确做法是用 volume 把宿主机的目录挂载到容器里。比如把宿主机的/data/ollama挂到容器的/root/.ollama,这样模型文件持久化在宿主机上,容器重建也不影响。
第三是端口暴露。Ollama 默认监听 11434 端口,你需要把它映射到宿主机,比如-p 11434:11434。但要注意,如果只在内网用,映射到127.0.0.1:11434更安全,避免被同网段的其他机器随意访问。
一个典型的启动命令大概是这样:
docker run -d \ --name ollama \ --gpus all \ -p 127.0.0.1:11434:11434 \ -v /data/ollama:/root/.ollama \ --restart unless-stopped \ ollama/ollama--restart unless-stopped这个参数很实用,服务器重启后容器会自动起来,不用手动干预。生产环境强烈建议加上。
3.2 Agent 编排层的依赖与环境隔离
编排层是整个平台的“大脑”,它要处理用户输入、决定调用哪个工具、管理对话历史、拼接提示词。这部分通常是一个 Python 应用,依赖比较多,环境隔离尤其重要。
我的经验是,编排层不要和推理层塞进同一个容器。原因很简单:两者的依赖完全不同,推理层要 CUDA、要特定版本的推理框架,编排层要的是 Web 框架、HTTP 客户端、向量库 SDK。混在一起,依赖冲突几乎是必然的。分开两个容器,通过 Docker 网络通信,各自维护自己的依赖,清爽得多。
编排层的 Dockerfile 里,有几个点要特别注意。一是基础镜像的选择,用python:3.11-slim这种精简版能显著减小镜像体积,但要注意有些系统库(比如编译工具链)可能缺失,需要按需安装。二是依赖安装的顺序,先装变化少的依赖,再装变化多的,这样能充分利用 Docker 的层缓存,加快构建速度。三是把配置项通过环境变量注入,不要把密钥、地址硬编码在代码里。
注意:编排层调用推理层时,地址不要写
localhost。在容器里,localhost指的是容器自己,不是宿主机。如果两个容器在同一个自定义网络里,直接用服务名(比如http://ollama:11434)就能访问。
3.3 向量库与知识库的持久化
如果 Agent 需要“查内部文档”的能力,向量库就是必需品。它的作用是把文档切块、向量化、存起来,用户提问时做相似度检索,把最相关的片段喂给模型。
向量库的容器化部署本身不难,难的是数据持久化和性能调优。持久化方面,和模型权重一样,一定要用 volume 挂载到宿主机,否则容器一重建,索引就没了,重新 embedding 一遍可能要几个小时。
性能方面,向量库对内存比较敏感。如果知识库规模不大(几万条以内),内存里跑完全没问题。但如果上了百万级,就要考虑磁盘索引和内存的平衡。我的建议是先用小规模数据把流程跑通,观察内存占用,再决定要不要扩容。
还有一个容易被忽略的点:embedding 模型的选择。向量库本身不产生向量,向量是由 embedding 模型生成的。这个模型也要跑起来,可以复用推理层的服务,也可以单独部署一个小模型。如果复用推理层,要注意 embedding 请求和对话请求会争抢资源,高并发时可能互相影响。
3.4 用 docker-compose 把一切串起来
当服务超过两个,手敲docker run就开始变得难以维护了。这时候docker-compose.yml就是救星。它把所有的服务、网络、卷、环境变量集中在一个文件里,一条docker compose up -d就能全部拉起来。
一个典型的 compose 文件结构大概是这样:
services: ollama: image: ollama/ollama deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] volumes: - /data/ollama:/root/.ollama ports: - "127.0.0.1:11434:11434" networks: - ai-net agent: build: ./agent environment: - LLM_BASE_URL=http://ollama:11434 - VECTOR_DB_URL=http://qdrant:6333 ports: - "8080:8080" depends_on: - ollama - qdrant networks: - ai-net qdrant: image: qdrant/qdrant volumes: - /data/qdrant:/qdrant/storage networks: - ai-net networks: ai-net: driver: bridge这里有几个设计考量值得说明。depends_on保证启动顺序,但它只保证容器启动,不保证服务就绪,所以编排层的代码里最好加上重试逻辑。自定义网络ai-net让三个服务能用服务名互相访问。GPU 资源通过deploy.resources声明,这是 compose 规范里指定 GPU 的方式。
4. 完整部署流程与关键环节实现
4.1 环境准备:驱动、Docker 与工具链
正式动手前,把基础环境检查一遍,能省掉后面一大堆莫名其妙的报错。
第一步是确认显卡驱动。在宿主机上执行nvidia-smi,能看到显卡型号、驱动版本、CUDA 版本就说明驱动没问题。如果这条命令报错,先去装驱动,别往下走。
第二步是装 Docker 和 Docker Compose。现在 Docker 官方推荐用docker compose(带空格,V2 版本)而不是老的docker-compose。装完之后用docker --version和docker compose version确认。
第三步是装 NVIDIA Container Toolkit。这是让容器能用上 GPU 的关键。装完之后,用一条测试命令验证:
docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi如果这条命令能输出显卡信息,说明容器访问 GPU 的链路是通的。如果报错,多半是 toolkit 没装好或者 Docker 服务没重启。
第四步是规划目录。我习惯在宿主机上建一个/data/ai-platform目录,下面分ollama、qdrant、agent几个子目录,分别挂载给对应的容器。这样所有持久化数据集中管理,备份和迁移都方便。
4.2 拉取模型与首次推理验证
环境就绪后,先把模型拉下来。以 Ollama 为例,进入容器执行拉取命令:
docker exec -it ollama ollama pull deepseek-r1:7b这里模型名称和版本要根据你的实际需求选。7B 版本对显存要求相对友好,适合先跑通流程。拉取过程可能要几分钟到几十分钟,取决于网络和模型大小。
拉完之后,直接在容器里做一次推理测试:
docker exec -it ollama ollama run deepseek-r1:7b "用一句话解释什么是容器"如果模型能正常回复,说明推理层没问题。这一步很重要,它把“模型能不能跑”和“编排层能不能调”两个问题隔离开了。如果这里就失败,那问题一定在推理层,不用去怀疑编排层。
实操心得:首次加载模型时,显存占用会有一个爬升过程,
nvidia-smi里能看到显存逐渐被占满。如果显存不够,模型加载会失败或者退到 CPU。这时候要么换更小的模型,要么用量化版本。别硬扛,换模型比调参数快得多。
4.3 编排服务的构建与联调
推理层通了之后,开始搞编排层。假设你已经有了一个 Agent 应用的代码,接下来就是把它容器化。
Dockerfile 我一般这么写:
FROM python:3.11-slim WORKDIR /app # 先装系统依赖,这层变化少,放前面利用缓存 RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ && rm -rf /var/lib/apt/lists/* # 再装 Python 依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 最后拷代码,这层变化最频繁 COPY . . EXPOSE 8080 CMD ["python", "main.py"]这个顺序的核心逻辑是:变化越少的层放越前面,这样改代码时不用重装依赖,构建速度快很多。我见过有人把COPY . .放在最前面,结果每次改一行代码都要重装所有依赖,构建一次好几分钟,效率极低。
构建并启动:
docker compose up -d --build agent启动后看日志:
docker compose logs -f agent重点看它有没有成功连上推理层和向量库。如果报连接错误,先检查服务名对不对、网络通不通。可以在 agent 容器里curl http://ollama:11434测试一下。
4.4 内网访问与安全边界设置
服务都跑起来后,最后一步是让内网用户能访问。这里有个安全边界的问题要想清楚:是只让本机访问,还是让整个内网访问?
如果只是自己调试,端口映射到127.0.0.1就够了。如果要让内网同事用,就得映射到0.0.0.0或者宿主机的内网 IP,同时确认防火墙放行。但要注意,一旦开放到内网,就意味着同网段的任何人都能访问,如果 Agent 能查敏感数据,这就存在风险。
我的做法是加一层简单的认证。可以在编排层前面放一个反向代理(比如 Nginx),配置基础认证;或者在应用层加一个 token 校验。局域网不等于绝对安全,该有的边界还是要有。
另外,如果内网有多个网段,要注意路由是否可达。有时候服务起来了,但另一个网段的同事访问不了,问题往往出在路由或防火墙,而不是服务本身。
5. 常见问题与排查技巧实录
5.1 容器启动失败与显存不足的排查
这是最常见的一类问题,我把排查思路整理成一张表,方便对照。
| 现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 容器启动后立即退出 | 命令错误或依赖缺失 | docker logs <容器名> | 看日志最后几行 |
| 模型加载失败 | 显存不足 | nvidia-smi看显存 | 换小模型或量化版 |
| 容器看不到 GPU | toolkit 未装或未重启 | 跑 GPU 测试命令 | 重装 toolkit 并重启 Docker |
| 端口被占用 | 宿主机已有服务占用 | ss -tlnp | grep 端口 | 换端口或停掉冲突服务 |
| 容器间访问不通 | 不在同一网络 | docker network inspect | 加入同一自定义网络 |
显存不足这个问题特别值得展开说。很多人看到“CUDA out of memory”就慌了,其实先别急着换硬件。第一步是确认到底需要多少显存,用nvidia-smi观察加载过程中的峰值。第二步是考虑量化,4-bit 量化能把显存需求降到原来的三分之一左右,对推理质量的影响在可接受范围内。第三步才是考虑换更小的模型或者加卡。
5.2 推理速度慢的性能调优
推理慢的原因有很多,得逐个排除。
如果模型跑在 CPU 上,那慢是必然的。先确认nvidia-smi里能看到推理进程占用 GPU。如果 GPU 利用率很低,说明瓶颈不在 GPU,可能在数据加载或者网络传输。
如果 GPU 利用率高但速度还是慢,可能是模型太大或者 batch 设置不合理。并发场景下,适当增大 batch size 能提升吞吐,但会增加显存占用和单次延迟,需要权衡。
还有一个容易被忽略的点是首次推理的“预热”。模型刚加载完,第一次推理往往特别慢,因为要初始化各种缓存。这是正常现象,多跑几次就稳定了。生产环境可以在启动后主动跑一次预热请求,避免第一个真实用户等待过久。
5.3 数据持久化与备份的注意事项
数据丢失是自建平台最痛的坑,没有之一。我见过有人辛辛苦苦 embedding 了几万条文档,结果容器重建时忘了挂 volume,全没了。
核心原则就一条:所有需要保留的数据,都必须挂载到宿主机。模型权重、向量库索引、对话历史、配置文件,一个都不能少。
备份策略上,我建议至少做到两点。一是定期把宿主机的数据目录打包备份到另一块盘或者另一台机器。二是把docker-compose.yml和所有 Dockerfile 纳入版本管理,这样即使机器彻底挂了,换台机器也能快速重建。
提示:向量库的备份要注意一致性。如果备份时正好有写入操作,可能备份到不完整的数据。稳妥的做法是先停掉写入服务,再备份,或者用向量库自带的快照功能。
5.4 内网访问异常的定位思路
内网访问不了,问题可能出在四个地方:服务本身、容器网络、宿主机防火墙、网络路由。排查要按顺序来,从近到远。
先在宿主机上curl localhost:端口,确认服务本身是活的。然后在同网段另一台机器上curl 宿主机IP:端口,确认端口映射和防火墙没问题。如果这一步不通,检查防火墙规则和端口绑定地址。如果同网段通了但跨网段不通,那就是路由问题,得找网络管理员。
我踩过的一个坑是:端口映射写成了127.0.0.1:8080:8080,结果只有宿主机自己能访问,内网其他机器全都不行。后来改成0.0.0.0:8080:8080才解决。这个细节在调试阶段很容易忽略,因为自己在宿主机上测试一切正常。
6. 后续扩展与个人经验分享
6.1 从单机到多机的平滑演进
一开始用一台机器把所有服务塞一起,是最省事的做法。但当用户变多、知识库变大,单机迟早会扛不住。这时候可以考虑把推理层单独拆到一台带显卡的机器,编排层和向量库留在另一台。
Docker 的好处在这里体现得很明显:拆分时只需要改 compose 文件里的服务地址,把http://ollama:11434改成http://内网IP:11434,其他代码几乎不用动。这种“配置驱动”的架构,让扩展变得平滑很多。
再往上走,如果推理需求很大,可以考虑多台推理机 + 一个负载均衡。但这已经超出“轻松搭建”的范畴了,属于生产级集群的领域,需要更系统的规划。
6.2 我踩过的几个真实坑
说几个我印象最深的教训,都是文档里不会写、只有自己踩过才知道的。
第一个是镜像体积。一开始我用python:3.11完整版做基础镜像,构建出来的镜像好几个 GB,推送到内网镜像仓库慢得要命。后来换成slim版,体积直接砍半。再后来把 pip 缓存清理掉,又小了一圈。镜像体积这件事,在局域网环境里尤其重要,因为内网带宽往往不如公网。
第二个是时区问题。容器默认是 UTC 时间,日志里的时间戳和本地差好几个小时,排查问题时特别容易看错。解决办法是在 Dockerfile 里设置TZ=Asia/Shanghai,或者在 compose 里注入环境变量。这个小细节不注意,排查问题时能把你绕晕。
第三个是日志膨胀。容器跑久了,日志文件能把磁盘占满。一定要配置日志轮转,在 compose 里加上logging配置,限制单个日志文件大小和保留数量。这个坑我是被磁盘告警叫醒的,半夜爬起来清日志的经历实在不想再来一次。
6.3 给不同阶段读者的建议
如果你是刚接触这块的新手,我的建议是先用 Ollama 把“模型能跑、能对话”这件事跑通,别一上来就追求 vLLM 的高性能。跑通之后,再逐步加上 Agent 编排、向量库、Web UI。每一步都验证通过再往下走,比一次性全堆上去然后面对一堆报错要高效得多。
如果你已经有一定基础,正在为团队搭建平台,那重点应该放在可维护性和可复现性上。compose 文件、Dockerfile、配置项全部纳入版本管理,写清楚每个参数为什么这么设。这样即使你休假了,同事也能照着文档把环境重建起来。
如果你是在做技术预研,需要快速验证想法,那就怎么快怎么来。用现成的镜像,用最小的模型,先把产品逻辑跑通。等思路验证了,再考虑工程化的问题。预研阶段追求的是速度,不是完美。
这套 Docker 化的局域网 AI Agent 方案,我自己在不同项目里反复用过,最大的感受就是“确定性”。环境是确定的,部署流程是确定的,出问题时的排查路径也是确定的。这种确定性,在自建平台这件事上,比任何性能数字都让人安心。