Docker+DeepSeek Harness:局域网内搭建私有化AI Agent平台实战
1. 为什么要用 Docker 在局域网里跑一个 AI Agent 平台先说个我自己的场景。有段时间我需要在公司内网做一套内部的知识问答与自动化辅助工具数据完全不能出网但团队又希望用上大模型的能力。直接调用线上 API 肯定不行于是我在本地服务器上尝试各种部署方案。折腾了一圈之后最终的落地方案就是用 Docker 部署 DeepSeek Harness把所有推理与智能体调度全部放进局域网。标题里提到的 AI Agent 平台本质上就是一套能让模型不仅回答问题、还能调用工具、拆解任务、串联多步骤动作的运行环境。很多人第一次听到“Harness”这个词会觉得陌生它直译过来是“线束”“挽具”在 AI 工程领域通常指“为模型搭建的运行时控制框架”。DeepSeek Harness 可以理解为围绕 DeepSeek 系列模型打造的一整套 Agent 执行框架模型推理由本地完成Agent 的规划、工具调用、上下文管理由框架完成两者通过统一的接口协作。你可以在浏览器里和它对话也可以让它在后台按计划执行任务再通过 API 把结果暴露给其他系统。适合谁参考如果你和我一样——手头有一台还算像样的服务器想把大模型能力变成局域网里一个真正能用的“智能员工”又不想碰那些需要复杂编译和裸机配置的方案这篇东西应该能帮你省不少时间。文章后面会涉及完整的 Docker Compose 编排、模型推理引擎选型、Agent 平台接入方式、局域网暴露细节和真实排错过程。我默认你已经会基本的 Docker 操作比如docker compose up这种级别。有一点必须先说清楚标题里写“轻松搭建”但真正的轻松来自于选型正确。我最早试图直接在宿主机上装 Python 环境、手动拉模型、手动起服务结果依赖冲突就耗掉一个晚上。换到 Docker 以后大部分环境问题就变成了写配置文件的问题。下面我把整个思路从头到尾拆开讲。2. 动手前的硬件评估与架构选型逻辑2.1 先认清自己的硬件底牌局域网部署和公网调用完全相反公网关注 API 延迟和费用局域网关注硬件能不能扛住。DeepSeek Harness 本身对 CPU 内存的要求不算极端真正的压力在模型推理上。我建议先跑一个简单的检查命令nvidia-smi lscpu free -h df -h /var/lib/docker这三条命令分别确认 GPU 型号与驱动、CPU 核数、内存容量以及 Docker 数据目录的磁盘余量。以我实际经验为例一台配备 24GB 显存的 GPU、32GB 内存、8 核 CPU 的服务器跑 DeepSeek 系列的量化版模型并支撑 3 到 5 个并发会话基本流畅。如果显存只有 8GB 或 12GB也不是不能玩但模型规模就要降档并做好推理时延变高的心理准备。这里有一个容易忽略的点DeepSeek 官方发布过多种参数规模的模型从轻量级到重量级都有。参数规模越大显存需求越高。同样一个模型FP16 精度和 4bit 量化所占空间完全不同。假设你的显存是 24GB跑 14B 级别模型的 4bit 量化版本大约占用 8 到 10GB余量充足上下文窗口还能开大一些。如果是 32B 级别4bit 量化后也要占用 16GB 以上这时候并发能力会明显下降。所以先用硬件倒推模型选型而不是先选模型再把硬件往上硬凑——这个顺序颠倒了后面全是被动。2.2 推理引擎为什么用 vLLM 而不是直接跑 PythonDeepSeek Harness 这类 Agent 平台本身不做推理它需要连接一个推理后端。目前主流选择无非是 vLLM、Ollama、llama.cpp 这几类。我最终选择 vLLM 的原因很简单吞吐量高、支持 OpenAI 兼容接口而且 DeepSeek Harness 的默认配置里对 vLLM 适配最完整。很多人会用 Ollama因为它上手极其简单一条命令就能拉模型。但真的把它接入 Agent 平台之后你会遇到一些别扭的地方Ollama 对并发请求的处理偏向串行函数调用Function Calling的兼容度在不同模型间表现不稳定。Agent 平台要频繁调用工具函数调用能力是刚需这一块 vLLM 稳得多。当然如果你只是想先体验一下效果内存不足、没有高性能 GPU用 Ollama 也未尝不可。但想跑成一个长期稳定服务的平台级底座我认为 vLLM 更合适。DeepSeek Harness 官方仓库里也提供了 vLLM 的 Dockerfile 和启动脚本照着做就能少踩不少坑。2.3 整体架构三个容器各司其职整个系统的容器编排我建议分成三个角色容器角色承载内容关键资源需求推理引擎容器vLLM 服务端、模型权重、分词器显存、GPU 直通Agent 平台容器DeepSeek Harness、任务调度、工具注册中心CPU、内存、网络辅助服务容器向量数据库、对象存储、日志收集器磁盘、内存把它们拆开的理由很实际推理引擎需要 GPUAgent 平台不一定需要Agent 平台可能随时更新版本推理引擎要保持稳定不动。拆开后你可以单独重启任何一个组件而不影响其他组件。这个架构在出现问题的时候排查边界非常清晰。辅助服务里的向量数据库并不是必须的但如果你打算让 Agent 具备“长期记忆”或对接本地知识库就建议加上。DeepSeek Harness 里可以把向量数据库作为工具注册给 Agent让它自动检索相关内容再生成回答。后面第 5 节我会展开讲工具注册的概念。3. 一步步搭建Docker Compose 配置与实际部署过程3.1 目录结构设计和关键镜像选择我建议按下面的目录结构组织整个项目deepseek-harness-stack/ ├── docker-compose.yml ├── .env ├── models/ │ └── (模型权重存放目录) ├── data/ │ ├── chroma_db/ │ └── logs/ └── config/ ├── harness_config.yaml └── tools_config.json这样组织的好处是模型权重和运行数据都放在宿主机的固定目录里容器可以随时删掉重建数据不丢。尤其是models/目录如果你手动下载过模型就知道了几个 GB 甚至几十 GB 的权重文件一旦容器内临时文件被清理重新下载非常痛苦。镜像选择方面我用的核心镜像大致如下推理引擎vllm/vllm-openai配上对应 CUDA 版本标签Agent 平台DeepSeek Harness 相关的镜像需要构建或从仓库拉取具体看官方说明向量数据库chromadb/chroma轻量级适合局域网个人使用关于镜像版本我的经验是不要盲目追最新标签latest建议锁定到你验证通过的版本号。因为大模型生态的组件更新非常快昨天还能跑的依赖组合今天可能就因为某个依赖升级而崩掉。用固定的镜像版本至少能保证你三个月后重新部署时依然能复现。3.2 手写 docker-compose.yml 并解释每个关键字段下面是一份可参考的 Compose 配置。仍然以开头的服务器配置为例我注释了每个关键字段的作用version: 3.8 services: vllm: image: vllm/vllm-openai:latest container_name: deepseek-vllm runtime: nvidia environment: - CUDA_VISIBLE_DEVICES0 - VLLM_USE_V21 command: --model /models/deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct --served-model-name deepseek --port 8000 --max-model-len 8192 --gpu-memory-utilization 0.9 --enforce-eager volumes: - ./models:/models ports: - 8000:8000 restart: unless-stopped harness: image: your-registry/deepseek-harness:latest container_name: deepseek-harness depends_on: - vllm environment: - HARNESS_MODEL_BASE_URLhttp://vllm:8000/v1 - HARNESS_MODEL_NAMEdeepseek - HARNESS_DATA_DIR/data volumes: - ./data:/data - ./config:/config ports: - 8080:8080 restart: unless-stopped chroma: image: chromadb/chroma:latest container_name: deeepseek-chroma volumes: - ./data/chroma_db:/chroma/chroma ports: - 8001:8000 restart: unless-stopped关于这段配置有几个细节值得展开。首先是runtime: nvidia——如果你的 Docker 版本较老需要先配置 NVIDIA Container Toolkit 才能在容器里使用 GPU。大多数 Linux 发行版上只需要安装nvidia-container-toolkit和nvidia-container-runtime然后重启 Docker。现在新版本 Docker 里也可以用--gpus all参数但 Compose 文件里用runtime: nvidia的写法兼容性更好。其次是 vLLM 的启动参数--gpu-memory-utilization 0.9表示允许 vLLM 使用 90% 的显存来缓存 KV Cache这样做能提升推理速度但也会挤占其他应用的空间。如果服务器上还有别的 GPU 任务建议调到 0.7 以下。--enforce-eager会关闭 CUDA Graph 加速存在推理速度变慢的代价但能减少显存碎片。如果你的 GPU 显存较小这个参数能避免启动时直接 OOMOut of Memory内存耗尽。3.3 启动、验证与基础调优执行部署只需要一条命令docker compose up -d然后看日志docker compose logs -f vllm docker compose logs -f harnessvLLM 首次启动时会加载模型权重这个过程的时间取决于磁盘速度和模型大小。我遇到过启动几分钟后日志里一直停留在“Loading model”的情况这通常是因为模型权重在机械硬盘上读取太慢。解决方案很直接把模型目录放到 SSD 上或者使用--swap-space参数做部分模型加载。验证推理是否可用可以发送一个简单的请求测试curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d {model:deepseek,messages:[{role:user,content:你好}],max_tokens:100}如果返回了正常的 JSON 响应推理引擎这一步就算通了。然后到 Harness 的 Web 界面http://localhost:8080上新建一个会话选择配置好的模型试着让它“介绍自己”。这一步通了整个系统就已经工作。后面再来加工具和知识库能力。4. 让 Agent 真正“会干活”工具注册与任务编排机制4.1 工具注册到底是什么意思如果你只是把 DeepSeek Harness 当成一个聊天机器人来用那完全不需要懂工具注册。但标题里写的是 AI Agent 平台Agent 和机器人的本质区别就在于它不止“聊天”还能“做事”。做事的途径就是调用工具。工具注册的过程很好理解你写一段代码定义一个函数比如“查询本地数据库”然后把函数名、参数说明、功能描述告诉 Harness。当用户对 Agent 说“帮我统计上个月的订单量”时模型会自行判断需要调用query_database这个工具从参数里提取“上个月”这个时间范围生成数据库查询语句执行完后把结果再汇总成自然语言回复。这个过程的核心环节叫“函数调用”Function Calling它依赖模型对工具描述的理解能力。DeepSeek Harness 中的工具描述通常写成 JSON Schema 格式。这里是一个简单的工具定义例子{ name: get_weather, description: 查询指定城市的天气情况, parameters: { type: object, properties: { city: { type: string, description: 城市名称如北京、上海 } }, required: [city] } }4.2 在 Harness 中开放自己的自定义接口除了内置工具我强烈建议你学会接入自定义工具。以我做的内部辅助工具为例我写了一个查询某内部系统的工具步骤分五步在 Harness 配置目录里的tools_config.json中注册工具元信息。写一个 HTTP 服务端点比如http://harness-tools:9000/api/query_internal_system。在 Harness 控制台新建工具时把 OpenAPI 风格的描述文件上传或粘贴。在会话里测试让 Agent 调用该工具并返回结果。把工具权限绑定到特定用户组避免任意会话都能触发。整个过程并不需要改动 Harness 平台本身的代码它会把外部 HTTP API 映射为内部工具调用。这意味着什么只要有 HTTP 接口任何系统都能被 Agent“拿捏”飞书/钉钉机器人、内部报表系统、运维监控平台、数据库查询器……一个个接进去之后Agent 就不再是孤立的聊天模型而是一个真正的跨系统调度中心。4.3 任务拆分与上下文管理工具调用只是 Agent 能力的一半另一半是任务编排。DeepSeek Harness 里提供一个重要能力叫“多步骤工作流”。你可以定义一个工作流先调用知识库全文检索再根据检索结果写摘要然后把摘要发送到指定 webhook。模型会按照你给定的步骤依次执行每执行完一步把结果追加到上下文里再决定下一步动作。上下文管理是这个环节最容易出问题的地方。长时间对话后历史消息累积很快就会顶到模型上下文窗口的上限。我一般建议的做法是启动时设置合理的max_model_len不要贪大8192 基本够用。使用 Summery 机制超过一定轮次后把早期对话压缩成摘要而不是全部保留。利用向量数据库做长期记忆重要的历史结论写入向量库需要时检索而不是每次都全量带入对话。如果你发现 Agent 跑着跑着开始忘记对话早期给出的指令或者重复犯错大概率是上下文被截断或者被工具返回的大段文本冲掉了。这时候我会打开 Harness 的会话调试面板查看完整的历史消息序列定位是哪一步把上下文撑爆的。5. 局域网接入实操多端访问、安全策略与 API 对接5.1 内网访问的两种方式局域网访问本质上就是要让同一网络内的其他设备能通过 IP 访问到 Harness 的 Web 界面和 API。默认情况下Docker 会把容器端口映射到服务器本机的0.0.0.0所以同一局域网内的其他电脑直接通过http://服务器IP:8080就能访问 Harness 界面。但这里有一个经常被忽视的问题如果你的服务器装了防火墙比如ufw要记得放行端口sudo ufw allow 8000/tcp sudo ufw allow 8080/tcp sudo ufw allow 8001/tcp如果你希望更安全一点让 Agent 平台核心接口只允许特定 IP 访问可以在 Compose 文件里改端口映射为ports: - 192.168.1.100:8080:8080这样只有来自192.168.1.100这个地址的请求才能访问 8080 端口。对于不熟悉网络配置的人来说这也是最直接的访问控制手段。注意别只盯着容器端口宿主机防火墙、路由器 ACL、交换机端口隔离都可能成为访问链路里的阻塞点。5.2 通过反向代理统一入口如果你的服务器上同时跑了很多容器服务端口一多就容易乱。我试过直接用 IP 加端口访问时间一长自己都分不清哪个端口对应哪个服务。后来我加了一层反向代理如 Nginx 或 Caddy统一走 80/443 端口按域名或路径分发到不同容器。Caddy 在局域网环境里很好用自动生成自签证书或直接用 HTTP配置量很小。示例配置harness.localhost { reverse_proxy deepseek-harness:8080 } vllm.localhost { reverse_proxy vllm:8000 }配好以后你只需要记住http://harness.localhost就足够了。而且用域名代替 IP:端口的方式在对接其他系统的时候也更直观。5.3 给上层系统暴露 API局域网内其他应用如果想调用 Harness 的 Agent 能力通常有两种模式。第一种模式最简单把 Harness 的对话接口直接暴露成 OpenAI 兼容格式。大多数 Agent 平台的接口设计都向 OpenAI 的接口风格靠拢直接替换 base_url 就可以。例如你的 Python 程序里只需要把原来的base_url改成http://服务器IP:8080/v1就能把模型从云端切到本地。第二种模式是事件回调。Harness 支持异步任务你提交一个任务后立即拿到任务 ID等任务执行完成后 Harness 会主动向预先注册的 Webhook 地址发送结果。这种模式适合耗时较长的多步骤工作流比如“拉取上个季度的数据并生成分析报告”这种可能运行几分钟的任务。对接方式的取舍取决于你的需求。如果是简单对话补全用同步接口如果是复杂工作流一定要用异步加回调不然 HTTP 请求超时的坑会一直缠着你。6. 避坑实录我从部署到稳定运行踩过的那些坑6.1 镜像拉取失败与依赖版本错位国内网络环境下镜像拉取偶尔会遇到超时这个可以用配置仓库加速器来解决属于常规操作。真正需要警惕的是镜像之间”默认依赖不一致”的问题。具体来说DeepSeek Harness 的一个较新版本升级了底层某依赖库的 API但我拉的 vLLM 镜像是旧版本两者请求路径不兼容。结果表现为Harness 能启动但一发起对话请求就报 500 错误日志显示请求 vLLM 时字段校验失败。我排查了很久才定位到原因是某个参数在新版 Harness 里从可选变成了必填而旧版 vLLM 不认识这个参数直接拒了。解决方式很讲究先确定 Harness 版本要求的 vLLM 最低版本再固定镜像 tag。不要两个组件都用latest。这也是我前文建议锁定版本号的核心原因——在这个生态里“新”并不总等于“稳定兼容”。6.2 GPU 显存不足与并发冲突有一段时间我调大了上下文窗口同时允许多个会话并行结果 vLLM 容器直接崩溃重启后又是崩溃。查了日志发现是显存不足且某个 CUDA 单元的状态没有正确清理导致重启后依然分配不到显存。解决这类问题有三个手段可以组合使用下调--gpu-memory-utilization比如从 0.9 调到 0.7。给 vLLM 容器设置内存限制mem_limit避免无节制占用。重启时加上stop_grace_period让容器优雅退出确保 CUDA context 清理干净。一个被我忽略很久的真相是docker compose restart不等于彻底清理 GPU 显存。如果你发现显存明明没占用但新容器启动报“No available memory”大概率是上一个容器没有彻底释放。这时候用docker compose down再启动而不是restart效果好得多。6.3 局域网里 Agent 频繁罢工最大嫌疑是网络代理还有一次特别蹊跷的故障Harness 的对话功能时好时坏有时候能回答有时候直接卡住。我一开始怀疑是模型问题后来打开日志看到 Harness 在尝试调用外网某个服务做向量化而这个调用在内网环境里根本不通于是一直在等待超时。这一步卡住整个对话流程就停住了。如果你也在内网部署建议提前检查 Harness 依赖的外部服务。最常见的有两类一是文本向量化模型接口二是某些插件需要联网下载资源。解决方案是提前把模型权重放到本地目录并确认 Harness 配置里所有模型地址都指向内网可达的位置。否则 Agent 表现会相当“人格分裂”时灵时不灵没有确定规律排查起来最花时间。7. 从“能跑”到“好用”持久化、监控与日常维护7.1 把会话数据与配置真正落盘容器本身是“用完即走”的所以任何有价值的数据都必须放到宿主机挂载卷里。我见过一位朋友因为只把模型放进了容器没挂载容器一升级几个月积累的对话记录和工具注册配置全部消失只能重新来一遍。建议至少把下面几类数据做持久化data/目录下的所有会话记录与任务状态。config/目录下的平台配置和工具注册信息。向量数据库的数据目录。日志文件方便回溯问题。如果你要升级 Harness 版本先备份整个数据目录再启动新镜像观察确认数据正常后再删除旧容器。这套习惯养成之后任何升级都不可怕。7.2 搭建轻量监控告警局域网里跑服务最怕“服务死了自己不知道”。我一直用docker compose ps配合一个简单的定时脚本做存活监控#!/bin/bash if ! curl -s http://localhost:8080/health | grep -q OK; then echo harness down at $(date) /var/log/harness_monitor.log docker compose restart harness fi抛砖引玉更正式的做法是接 Prometheus 抓取 vLLM 的指标或者用 uptime 类的工具定时探测。vLLM 自带/metrics端点能输出请求延迟、排队请求数、token 吞吐量等数据。通过 Grafana 面板可视化就能直观看到平台的运行状态。7.3 模型更新与迭代路径DeepSeek 模型本身也在持续迭代。当我拿到新版本模型权重后通常做法是先把权重放在models/目录下的新路径更新 Compose 文件里的command指向新路径启动一个新的测试容器用独立端口验证效果。确认无误后再切换主容器。这个过程之所以建议这样做是因为模型版本更替可能带来“行为变化”——有些旧版本工作良好新版本可能在特定任务上表现反而不如旧版。留一个过渡期新旧并行测试是对生产环境负责任的做法。8. 再聊几句心里话这套方案的价值边界与扩展想象说句公道话DeepSeek Harness 并不是一个“开箱即用的成品”它更像是一个类别的平台化中间层。它的价值在接入真实业务系统的那一刻才真正展现出来。我见过有人只把它当一个本地版 ChatGPT 用那当然也能用但如果你需要它去连数据库、调内部接口、跑定时任务、对接报表系统真正打通一两条链路后你会意识到——大模型部署本身不难难的是给它一个与真实世界互动的“身体”而 Harness 正好是这个身体的骨架。部署过程中的每一步我都踩过坑尤其是 vLLM 与 Harness 的版本兼容问题以及内网环境和外部服务依赖的隐性冲突。如果你准备在团队里落地这套方案我个人的建议是先从一个具体场景出发。不要一上来就追求全能 Agent而是选定一个任务比如“自动化汇总日报并发送到内部群”踏踏实实地把工具注册、任务编排、回调通知跑通再逐步扩展业务场景。当你真的把一个工具端到端打通之后你会对 Agent 的能力边界有更真实的体感——什么场景适合它什么场景还太勉强心里会有一本账。这种判断力比任何配置文件都宝贵。