最近在给内部环境搭 AI Agent 平台的时候,我选了 Docker 部署 DeepSeek Harness 这套组合。所谓 Harness 其实是我对“模型服务 + Agent 编排 + Web 管理界面”整套容器化方案的内部代号,它解决的痛点是:团队多人共用同一套 Agent 能力、数据不出内网、模型可以随时换。这篇文章不打算讲虚的,直接把我踩过的坑、写过的配置、验证过的方法都摊开来说,适合那些想在局域网里快速落地一个 AI Agent 平台的开发者或运维同学参考。
1. 为什么要在局域网里部署一套 AI Agent 平台
1.1 从“调接口”到“自托管”的转变
很多项目一开始都是直接调外部模型 API,确实省事,但用一段时间就会发现几个绕不开的问题:数据要出网,敏感信息不放心;调用有频率和并发限制,团队一多人就排队;成本跟着 token 走,测试环境随便跑几个任务账单就看懂。尤其当 Agent 要连内部数据库、读内部文档、执行一些自动化操作时,把模型推理放在局域网里是更稳妥的选择。
DeepSeek 这一类开源权重模型的价值就在这里——模型权重可以下载到本地,推理服务跑在自己的机器上。配合 Docker,我不用为每个成员都配一套 Python 环境,也不用担心某个人把模型版本搞乱。整个 Agent 平台收敛成一组容器,有统一的入口、统一的数据存储、统一的日志,这对团队协作来说太重要了。
Harness 在整个系统里做的是“编排层”的工作:用户通过界面创建 Agent,给 Agent 配置系统提示词、选择模型、挂载工具;Agent 收到任务后决定调用什么工具,再通过模型服务完成理解和生成。如果只有模型但缺少这一层,本质上你还是在跟“一个纯 Chatbot”对话,谈不上 Agent。
1.2 Docker 在这套方案里的角色
我一开始也犹豫过,是不是直接在宿主机上用 Python 虚拟环境 + Shell 脚本就行了?后来发现维护成本实在太高。模型推理服务依赖一堆 CUDA 库,Agent 编排服务需要特定版本的 Python/Node,数据库又要单独管理,任何一个依赖升级都可能牵连其他组件。用 Docker 以后,每个服务变成独立容器,彼此通过网络通信,坏了一个就单独重启,不影响其他服务。
Docker Compose 则是这套方案里最划算的编排工具。它不需要像 Kubernetes 那样搭建一整套控制平面,一条docker compose up -d就能把模型服务、Agent 服务、数据库、缓存全部拉起。对于单机或少量机器组成的局域网环境,Compose 的功能边界刚好够用。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 手动脚本 | 直观、灵活 | 依赖冲突多、回滚困难 |
| Docker Compose | 一键编排、可复现、易回滚 | 需要理解容器网络和数据卷 |
| Kubernetes | 弹性强、适合大规模 | 部署和维护成本高,小团队会淹没在 YAML 里 |
我选择 Compose 的另一个原因是后续升级特别方便。之前模型推理服务从 CPU 版本切到 GPU 支持,我只需要改 compose 文件里的设备参数和镜像标签,然后docker compose up -d重建相关容器,整个过程十分钟内搞定,宿主机上没有任何残留依赖。
2. 部署前的准备:硬件、软件与整体架构
2.1 硬件最低要求与推荐配置
先想清楚你要跑多大的模型,再决定硬件。DeepSeek 开源模型有不同参数规模的版本,量化之后体积差异很大。我的经验是:如果只是给三五个人做内部工具测试,一个 7B 量级的量化模型配合 16GB 内存也能跑起来,但要给到十几个人同时用,还是建议上 GPU 并把内存提到 32GB 以上。
| 组件 | 最低要求 | 推荐配置 |
|---|---|---|
| CPU | 8 核 | 16 核及以上 |
| 内存 | 16GB(跑 7B 量化会比较吃力) | 32GB 或 64GB |
| GPU | 可选 | 显存 12GB 及以上 |
| 磁盘 | 20GB 可用 | SSD 预留 100GB,用于模型和日志 |
| 网络 | 千兆局域网 | 千兆局域网 |
我实际测试下来,纯 CPU 跑 7B 模型,单轮对话可能要等十几秒,但如果任务只是批量处理文档摘要,吞吐量还能接受。GPU 的作用主要是把单次推理延迟压到一两秒以内,Agent 在做多轮工具调用时,体感差距会非常明显。
还有一个容易忽略的点:容器里的共享内存大小。模型推理服务有时会用到/dev/shm做缓存,默认 64MB 可能不够,我会在 compose 文件里给模型服务加上shm_size: '2gb',避免一些奇怪的运行时崩溃。
2.2 软件环境与端口规划
宿主机我推荐 Linux,Debian 或 Ubuntu 都行。需要安装 Docker Engine 和 Docker Compose 插件,版本不要太老,因为新版 compose 支持 GPU 资源声明和更完善的健康检查。Windows 和 macOS 也能跑,但文件挂载性能和网络模式会有差异,生产环境不建议。
端口规划上,我习惯让 Web 界面、Agent API、模型推理 API 分别占用不同端口,数据库和缓存只走容器内部网络,不绑定到宿主机端口。这样最外层只需要暴露两个端口,攻击面小很多。
| 服务 | 默认端口 | 绑定方式 |
|---|---|---|
| Web UI | 8000 | 宿主机 0.0.0.0:8000 |
| Agent Core API | 8080 | 宿主机 0.0.0.0:8080 |
| 模型推理 API | 11434 | 宿主机 127.0.0.1:11434 或内部网络 |
| PostgreSQL | 5432 | 仅容器内部网络 |
| Redis | 6379 | 仅容器内部网络 |
端口规划最怕的是一套服务里好几个子服务都默认监听 80 端口,然后互相冲突。把权限边界想清楚,后续加 Nginx 反向代理或者改防火墙规则都会更容易。
2.3 整体架构拆解
整个平台本质上是一条请求链路。用户打开 Web UI 后输入任务,浏览器把任务交给 Agent Core。Agent Core 负责理解任务、拆解步骤、调用工具,并在需要语言理解或生成时请求模型推理服务。PostgreSQL 保存用户、Agent 配置、会话记录,Redis 用来做缓存和轻量级任务队列。
如果还要做知识库问答,我会在架构里加一个向量数据库,比如 Qdrant 或类似组件,用来存文档切块后的向量。这一步不是必须的,但一旦加上,Agent 就能基于内部文档回答,而不是只能靠模型本身的记忆。Harness 的价值就是把模型、工具、记忆这三块拼起来。
我做一个生活化的类比:Agent Core 像是项目负责人,模型是随叫随到的专家,PostgreSQL 是档案室,Redis 是白板,工具集是团队手里的工具箱。Docker Compose 则像是一个标准化的办公室布局,所有东西摆到固定位置,谁要用直接去拿。
3. 用 Docker Compose 编排核心服务
3.1 服务清单与镜像选择
先确定需要哪些镜像。模型服务我用的是 ollama 镜像,因为它支持把 DeepSeek 模型一键拉取并暴露 HTTP API,对容器环境很友好。Agent Core 和 Web UI 我是从 Dockerfile 构建出来的,因为后面要集成自定义工具和插件,官方镜像往往满足不了需求。数据库和缓存则直接用官方 alpine 版本,体积小,启动快。
| 服务 | 镜像/构建来源 | 说明 |
|---|---|---|
| model-service | 本地推理服务镜像 | 加载 DeepSeek 模型并暴露 API |
| agent-core | 自定义 Dockerfile 构建 | 提供 Agent 编排 API |
| agent-web | 自定义 Dockerfile 构建 | 浏览器管理界面 |
| postgres | postgres:16-alpine | 存储业务数据 |
| redis | redis:7-alpine | 缓存与任务队列 |
镜像尽量锁定具体版本号,不要用 latest。之前我就吃过亏,某天重建容器时 latest 变了,模型服务启动方式也变了,导致线上姑且能跑但监控告警一堆。锁定版本之后,至少能保证“昨天能跑,今天也能跑”。
3.2 compose 文件关键配置解析
下面是一份我经过几轮修补后的核心配置,去掉了敏感信息,只保留结构。你不需要一字不差地抄,重点是理解每个服务为什么这么配。
services: model-service: image: ollama/ollama:0.1.48 container_name: harness-model shm_size: '2gb' ports: - "11434:11434" volumes: - ./data/models:/root/.ollama environment: - OLLAMA_KEEP_ALIVE=5m deploy: resources: reservations: devices: - capabilities: [gpu] restart: unless-stoppedshm_size前面说过,是给模型推理留共享内存余量。OLLAMA_KEEP_ALIVE=5m的意思是模型在内存里保持加载 5 分钟,避免每次请求都重新加载。GPU 那段deploy.resources是标准声明方式,如果宿主机没有 GPU,把整个deploy块删掉即可,模型服务会回落到 CPU 模式。
Agent Core 的配置要复杂一点,它需要知道模型服务、数据库、Redis 的地址。在 Compose 网络里,我直接用服务名作为主机名,所以环境变量里写的是http://model-service:11434,而不是写某个 IP。
agent-core: build: ./harness image: harness-core:latest container_name: harness-core ports: - "8080:8080" environment: - MODEL_ENDPOINT=http://model-service:11434 - DATABASE_URL=postgresql://harness:harness@postgres:5432/harness - REDIS_URL=redis://redis:6379/0 - TOOL_WHITELIST=web_search,calculator,db_query volumes: - ./config:/app/config - ./logs:/app/logs depends_on: model-service: condition: service_healthy postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stoppedTOOL_WHITELIST是我后面加的一个安全配置,用来控制 Agent 默认可以调用哪些工具,不在白名单里的工具即使注册了也不会被启用。这个做得比较保守,避免某个 Agent 被提示词注入后乱调内部工具。
数据库和缓存相对简单,把数据目录挂载出来就好。尤其数据库,如果不挂载数据卷,重建容器等于删库。
postgres: image: postgres:16-alpine container_name: harness-postgres environment: - POSTGRES_USER=harness - POSTGRES_PASSWORD=harness - POSTGRES_DB=harness volumes: - ./data/postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U harness"] interval: 5s timeout: 3s retries: 10 restart: unless-stopped redis: image: redis:7-alpine container_name: harness-redis command: ["redis-server", "--appendonly", "yes"] volumes: - ./data/redis:/data restart: unless-stoppedWeb UI 我习惯用容器内 Nginx 托管静态页面,再反代到 Agent Core。镜像构建时把前端代码复制到 Nginx 目录,运行时只需要暴露 80 端口映射到宿主机 8000。这样多一层的好处是,后面想加 HTTPS 证书,可以直接在 Nginx 层处理,不用改 Agent Core。
3.3 数据持久化与网络配置要点
Compose 默认会创建一个桥接网络,服务之间通过服务名互相访问,这是最省事的方案。我没有手动指定静态 IP,因为服务名解析已经足够稳定。真正要注意的是数据卷:模型、PostgreSQL、Redis、日志、配置,全部挂载到宿主机目录。容器可以随时删,数据得留住。
我记得第一次部署时,只挂了模型目录和数据库目录,日志没挂,结果某个 Agent 半夜循环执行任务,把容器日志写到内部磁盘,容器文件系统被写满。后来我统一规划了宿主机目录:
~/harness-deploy/ ├── config/ ├── data/ │ ├── models/ │ ├── postgres/ │ └── redis/ ├── logs/ └── docker-compose.yml离线环境也是需要考虑的场景。如果目标机器无法直接拉取镜像,可以在一台能拉镜像的机器上执行docker compose pull,然后用docker save把镜像打包成 tar 文件,拷贝到目标机器后用docker load导入。这个过程不复杂,但建议把镜像版本固定,否则导入和导出之间的镜像 ID 对不上,容易白折腾。
4. 一步步完成部署与初始化
4.1 目录初始化与镜像准备
我建议先创建目录结构,再复制项目代码。假设你的 Agent Core 代码和 Web UI 代码已经放在./harness和./harness-web目录下,那么顶层只有docker-compose.yml和一堆数据目录。
mkdir -p ~/harness-deploy/{config,data,logs} cd ~/harness-deploy # 把 agent-core 代码放到 harness 目录,web 代码放到 harness-web 目录 # 然后拉取基础镜像和构建自定义镜像 docker compose build docker compose pullbuild和pull分开做是有原因的:数据库、缓存、模型推理这类第三方镜像从仓库拉取,自定义镜像则在本机构建。如果构建过程依赖外部包的下载,建议先把构建缓存做好,避免每次docker compose up -d都重新构建。
4.2 启动服务与健康检查
启动服务这一步看起来简单,但第一次启动时我习惯不用-d,而是用前台模式看日志,方便及时发现配置错误。
docker compose up看到所有服务都正常启动后,再按 Ctrl+C 停掉,重新用docker compose up -d在后台运行。确认运行状态:
docker compose ps如果某个容器显示 unhealthy,不要急着重启,先看日志。我用得最多的命令是:
docker compose logs -f agent-core模型服务也需要尽快验证。假设模型推理服务暴露在 11434 端口,可以先请求一下本地标签接口,确认服务在线:
curl http://localhost:11434/api/tags接着拉取 DeepSeek 模型。不同来源可能命名不同,我示例里使用deepseek-r1:7b,你也可以根据自己的模型源调整名称。这一步会下载几百 MB 到几 GB 的文件,根据网络环境耐心等一下。
docker exec -it harness-model ollama pull deepseek-r1:7b拉取完成后,再请求一次标签接口,应该能看到模型已经在列表里。到这里,模型推理链路基本通了。
4.3 创建 Agent 并接入工具
打开 Web UI 后,第一件事是创建一个管理员账号。然后创建 Agent,需要填的内容通常包括:Agent 名称、系统提示词、基础模型、温度参数、启用的工具列表。系统提示词是 Agent 行为的核心,比如我内部有个“日志分析助手”,系统提示词会写清楚“只分析指定目录下的日志文件,不执行任何写操作”。
工具接入采用白名单机制。在 Agent Core 的环境变量里已经设置了TOOL_WHITELIST,但每个 Agent 还可以再单独勾选。工具本身以插件的形式注册,注册信息用 JSON 描述,简单示例:
{ "name": "db_query", "description": "对内部业务数据库执行只读 SQL 查询", "parameters": { "type": "object", "properties": { "sql": { "type": "string", "description": "要执行的只读 SQL" } } }, "readonly": true }配置完成后,我通常会做一个端到端测试:问 Agent“统计最近一小时的 ERROR 日志数量,并按模块分组”。正常情况下,Agent 会先调用工具查询日志,再把结果交给模型整理成可读结论。这一步能验证工具链路、模型服务和数据存储是否全部打通。
4.4 让局域网里的其他设备访问
服务跑通后,局域网访问是最后一步。核心是把宿主机的防火墙放行 Web UI 和 Agent API 端口。我用的是 8000 和 8080,所以需要保证这两个端口的 TCP 流量能进来。
如果你用的是带防火墙的 Linux,要注意只放行业务端口,别把数据库端口也放出来。我在实践中会直接限制来源网段,比如只允许办公网段访问 8080 端口,其他网段一律丢弃。这样即使有人扫描到宿主机,也无法直接触碰 Agent API。
访问地址就是:http://宿主机IP:8000。如果在浏览器里能打开登录页面,说明 Web UI 和 Agent Core 之间的通信正常。如果页面打不开,优先检查容器状态和防火墙规则,不要一开始就去改代码。
5. 常见问题排查与实战心得
5.1 模型服务启动失败的排查顺序
我遇到的模型服务问题,大多数出在启动阶段。最快的排查路径是先看容器状态,再按顺序检查端口、显存、模型文件。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 容器反复重启 | 端口被占用或配置错误 | docker compose logs model-service看具体报错,释放端口后重启 |
| 日志里出现 GPU 相关错误 | 容器无法访问 GPU | 确认宿主机 GPU 容器运行时已安装,compose 中保留完整设备声明 |
| 模型调用很慢或超时 | 模型仍在加载或配置过低 | 查看OLLAMA_KEEP_ALIVE,必要时调大,同时检查 CPU/内存占用 |
| 拉取模型失败 | 模型名称错误或磁盘不足 | 检查模型名称、磁盘剩余空间,确认路径挂载正确 |
有一次模型服务一直报“connection refused”,我以为是端口映射出了问题,后来才意识到是服务还没启动完成,Agent Core 已经尝试连接。所以我在depends_on里加了健康检查条件,确保模型服务真正就绪后才启动 Agent Core。这是 Compose 里最容易忽略的细节。
5.2 显存与内存不足时的处理
跑模型最怕资源不够。如果是显存不足,常见结果不是直接报错,而是容器被杀掉。排查时用dmesg或者docker inspect能看到容器退出信号,但最直观的还是看日志。
我的处理顺序是:先看看有没有更低精度的量化版本,换一个小一点的模型;如果模型不能换,就把并发量降下来。Agent Core 通常会并发处理多个任务,我通过给 Agent 配置任务队列上限来控制同时打到模型服务的请求数。另外,把OLLAMA_KEEP_ALIVE从 5 分钟调到 15 分钟,可以减少反复从磁盘加载模型的开销,虽然会增加常驻内存,但实际体验会顺很多。
还有一个内存亮点:PostgreSQL 和 Redis 也会占用内存。如果宿主机总内存只有 16GB,光跑 7B 模型和数据库就会很紧张。我建议在 compose 文件里限制数据库和缓存的可用内存,例如给 Redis 加上mem_limit: 512m,避免某个组件吃光内存后拖垮整机。
5.3 配置与日志里容易被忽略的细节
有几个细节,我几乎每次部署都要重新确认一遍。
容器时区非常重要。Agent 生成的日志时间如果和宿主机差 8 小时,排查问题时你会很痛苦。我在所有业务容器环境变量里加上了TZ=Asia/Shanghai,如果不是这个时区,记得按自己的时区修改。
数据库连接串里的密码如果直接写在 compose 文件里,会有泄露风险。我的做法是使用.env文件存储环境变量,并且把.env加入.gitignore。Compose 会自动读取.env文件填充${POSTGRES_PASSWORD}之类的变量,代码仓库里则完全不出现明文密码。
镜像标签也不要偷懒用latest。我在离线导入导出镜像时,吃过两次“latest 不可复现”的亏。现在所有第三方镜像都固定到具体版本,自定义镜像构建时也顺手打上构建日期,回滚的时候非常有底。
日志方面,建议统一日志格式。Agent Core 打印日志默认是自由文本,后来我改成 JSON 结构,方便在日志系统里按字段检索。如果是自建日志采集,这一步能省下很多解析成本。
5.4 一些使用上的实战心得
整套系统跑起来之后,有几条经验我想重点说一下。
第一,不要一上来就接一堆工具。先跑一个最小闭环:模型 + 一个搜索类工具 + 数据库存储,验证链路稳定后再加知识库和更多工具。我见过同事一口气配置了十几个工具,结果 Agent 经常选错工具,最后排查了半天发现是工具描述写得不够清晰。工具不在多,描述清楚更重要。
第二,Agent 的系统提示词决定行为边界。一定不要为了追求“智能”就把权限放得太开。比如让 Agent 能够执行 Shell 命令,就非常危险;如果必须使用,工具实现里至少要做白名单命令校验和危险命令拦截。我内部实际部署时,所有写操作默认禁用,只读操作也只在受控的测试环境开放。
第三,备份和恢复要提前演练。PostgreSQL 数据卷里的会话记录虽然不常看,但一旦丢失,所有 Agent 的调试历史都没了。我每周会定时导出数据库到一个归档目录,恢复时只需要用 PostgreSQL 官方工具从备份文件恢复即可。整个过程我可以在一小时内完成,这个能力平时用不上,但真出问题时能救命。
最后再说一个小技巧:如果想快速验证新模型,不要直接替换默认模型,而是新建一个 Agent,选择新模型,跑同一组测试任务。这样能同时对比多个模型的表现,不影响正在跑的正式任务。我最后选择的模型配置就是这么一轮一轮测出来的,而不是只看理论参数。