:Docker 部署详解——从零到生产环境的完整实战指南!)
1. 为什么第二大脑需要 Docker 化部署如果你已经跟着前两篇把 Hermes Agent 和 Obsidian 的联动跑通了大概率会遇到一个尴尬本地笔记本上跑得好好的换台机器就各种依赖报错或者你想让 Agent 7×24 小时盯着 Vault 做索引、跑摘要总不能一直开着笔记本。这时候 Docker 部署就不是「炫技」而是把第二大脑从玩具变成基础设施的必经一步。Hermes Agent 本质上是一个「常驻服务 文件监听 模型调用」的组合体。它需要 Python 运行时、Node 前端、PostgreSQL 存元数据、Redis 做任务队列还要能读写你的 Obsidian Vault 目录。手工装这一套光是 Python 版本和 psycopg2 编译就能耗掉你一个下午。Docker Compose 把这些组件打包成互相隔离又互相连通的容器一条docker compose up -d就能拉起整套环境升级时docker compose pull换镜像即可回滚也只是换个 tag。这篇是「Hermes Agent Obsidian 打造第二大脑」系列的第三篇聚焦 Docker 生产级部署。我会从镜像拉取、数据卷与网络规划讲起给出可直接复制的docker-compose.yml和.env配置再逐步验证请求、排查常见报错。模型服务这一层我用 TaoToken 统一 Key 和 API 通道接入这样 OpenAI、Anthropic、本地 Ollama 都能走同一个入口省得在多个平台之间来回切换配置。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 后面配置里会具体写怎么填 Base URL 和 Model ID。适合谁看已经有一台能跑 Docker 的服务器本地 NAS、云主机、家里的迷你主机都行想让 Obsidian 第二大脑具备自动索引、自动摘要、跨设备同步能力的同学。如果你还没装 Docker先执行curl -fsSL https://get.docker.com | sh把 Docker 和 Compose 插件装上再往下走。2. TaoToken 前置准备统一 Key 与 API 通道在写 compose 文件之前先把模型服务这一层理清楚。Hermes Agent 的 API 容器和 Worker 容器都需要调用大模型如果每个容器里塞不同的厂商 Key配置会散得到处都是换模型时改到崩溃。我的做法是所有模型请求统一走 TaoToken 的 API 通道容器里只认一个 Base URL 和一个 Key。TaoToken 在这里扮演的是「统一模型网关」的角色。你不需要在 Hermes 里为 OpenAI、Anthropic 各写一套适配只要把OPENAI_API_BASE指向 TaoToken 的 API 地址把OPENAI_API_KEY填成 TaoToken 生成的 Key模型名换成对应的 Model ID 就行。这样 Hermes 的代码逻辑完全不用动切换模型只是改一个环境变量。具体操作路径是这样的先到控制台创建一个 API Key然后确认你要用的模型 ID。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建完 Key 之后先别关页面复制下来后面.env里要用。这里有个容易踩的坑很多人以为 Base URL 要填到/v1/chat/completions这一层其实不用。Hermes 内部用的是 OpenAI SDK 风格Base URL 填到/v1就够了SDK 会自己拼路径。TaoToken 的 API 根地址是 https://taotoken.net/api 所以环境变量里写https://taotoken.net/api/v1即可。如果你用的是 Anthropic 风格的调用Hermes 也支持单独配ANTHROPIC_BASE_URL同样指向 TaoToken 的对应端点。模型 ID 这块建议先在模型对话页面确认一下你要用的模型能不能正常响应。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 随便发一句「你好」看返回是否正常。确认没问题后把模型 ID 记下来比如gpt-4o、claude-3-5-sonnet-20241022这类。Hermes 的.env里OPENAI_MODEL就填这个值。如果你打算长期跑 Agent 任务比如让 Hermes 定时扫描 Vault 做摘要、建索引那 Coding Plan 会更划算入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它的额度模型更适合这种持续调用的场景不用每次按量计费心里没底。把 Key 和 Model ID 准备好之后我们就可以进入 compose 配置环节了。记住三个东西Base URL 是https://taotoken.net/api/v1Key 是刚复制的那串Model ID 是你确认过能用的那个。这三件套在后面的.env和docker-compose.yml里都会出现。3. 可复制的 docker-compose 与 .env 配置这一节是全文的核心配置能直接复制粘贴用。先建工作目录mkdir -p ~/hermes-agent cd ~/hermes-agent mkdir -p data/vault data/postgres data/redis data/backups logs nginx/conf.d然后创建.env文件。注意这个文件不要提交到 Git里面全是敏感信息# 模型服务TaoToken 统一通道 OPENAI_API_KEYsk-你的TaoToken密钥 OPENAI_API_BASEhttps://taotoken.net/api/v1 OPENAI_MODELgpt-4o OPENAI_MAX_TOKENS4096 OPENAI_TEMPERATURE0.7 # 如果要用 Anthropic 风格 ANTHROPIC_API_KEYsk-你的TaoToken密钥 ANTHROPIC_BASE_URLhttps://taotoken.net/api ANTHROPIC_MODELclaude-3-5-sonnet-20241022 # 安全密钥用 openssl rand -base64 32 生成 JWT_SECRET替换成随机32字节 API_SECRET_KEY替换成随机32字节 # Obsidian Vault OBSIDIAN_VAULT_PATH/data/vault # 数据库 POSTGRES_DBhermes POSTGRES_USERhermes POSTGRES_PASSWORD替换成强密码 POSTGRES_HOSTpostgres POSTGRES_PORT5432 # Redis REDIS_HOSTredis REDIS_PORT6379 REDIS_PASSWORD替换成强密码 # 同步与 Worker SYNC_INTERVAL30 SYNC_STRATEGYkeep_both WORKER_CONCURRENCY5 WORKER_QUEUEhermes_tasks # 日志 LOG_LEVELinfo LOG_FORMATjson # 端口 WEB_PORT3000 API_PORT8080生成随机密钥的命令openssl rand -base64 32 # JWT_SECRET openssl rand -hex 32 # API_SECRET_KEY接下来是docker-compose.yml。这份配置把 API、Web、Worker、PostgreSQL、Redis 五个服务编排在一起网络用自定义 bridge数据卷全部挂到宿主机目录方便备份和迁移services: postgres: image: postgres:16-alpine container_name: hermes-postgres restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - ./data/postgres:/var/lib/postgresql/data networks: - hermes-network healthcheck: test: [CMD-SHELL, pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}] interval: 10s timeout: 5s retries: 5 start_period: 30s redis: image: redis:7-alpine container_name: hermes-redis restart: unless-stopped command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes volumes: - ./data/redis:/data networks: - hermes-network healthcheck: test: [CMD, redis-cli, -a, ${REDIS_PASSWORD}, ping] interval: 10s timeout: 5s retries: 5 api: image: hermes/hermes-api:latest container_name: hermes-api restart: unless-stopped env_file: .env environment: - POSTGRES_HOSTpostgres - REDIS_HOSTredis - OBSIDIAN_VAULT_PATH/data/vault ports: - ${API_PORT}:8080 volumes: - ./data/vault:/data/vault - ./logs:/app/logs depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - hermes-network healthcheck: test: [CMD, curl, -f, http://localhost:8080/health] interval: 15s timeout: 5s retries: 3 start_period: 40s worker: image: hermes/hermes-worker:latest container_name: hermes-worker restart: unless-stopped env_file: .env environment: - POSTGRES_HOSTpostgres - REDIS_HOSTredis - OBSIDIAN_VAULT_PATH/data/vault volumes: - ./data/vault:/data/vault - ./logs:/app/logs depends_on: api: condition: service_healthy networks: - hermes-network web: image: hermes/hermes-web:latest container_name: hermes-web restart: unless-stopped env_file: .env environment: - API_BASE_URLhttp://api:8080 ports: - ${WEB_PORT}:3000 depends_on: api: condition: service_healthy networks: - hermes-network networks: hermes-network: driver: bridge几个关键点解释一下。depends_on配合condition: service_healthy保证启动顺序PostgreSQL 和 Redis 先健康API 才启动API 健康后 Worker 和 Web 才启动。这样避免「数据库还没起来 API 就报连接失败」的经典问题。数据卷全部用./data/xxx绑定挂载而不是命名卷好处是你ls data/就能看到所有持久化数据备份时直接tar打包即可。Vault 目录挂载是重点./data/vault:/data/vault容器内路径和.env里的OBSIDIAN_VAULT_PATH保持一致。你本地的 Obsidian Vault 可以软链过来或者直接把 Vault 目录放到data/vault下。权限问题后面第五节会专门讲。启动命令docker compose pull docker compose up -d docker compose ps预期看到五个容器都是Up状态其中 postgres、redis、api 显示(healthy)。如果 api 一直starting等 40 秒左右再看健康检查有start_period。4. 验证请求与成功结果容器起来不代表服务能用得实际打几个请求确认。先看健康检查curl http://localhost:8080/health正常返回类似{ status: ok, version: 1.0.0, services: { database: connected, redis: connected, vault: accessible } }如果database是disconnected说明 API 连不上 PostgreSQL去查docker compose logs api | grep -i postgres。如果vault是inaccessible是挂载或权限问题看第五节。接着验证模型通道是否打通。这一步很关键因为 TaoToken 的 Base URL 或 Key 填错的话健康检查是看不出来的只有实际调用才会暴露curl -X POST http://localhost:8080/api/v1/chat \ -H Content-Type: application/json \ -H Authorization: Bearer $API_SECRET_KEY \ -d {message: 用一句话介绍 Obsidian, model: gpt-4o}预期返回{ status: success, reply: Obsidian 是一款基于本地 Markdown 文件的双向链接笔记工具。, usage: {prompt_tokens: 18, completion_tokens: 24} }如果返回 401是API_SECRET_KEY没对上如果返回model not found是OPENAI_MODEL填错了去模型对话页面确认一下模型 ID。如果返回连接超时检查OPENAI_API_BASE是不是https://taotoken.net/api/v1注意结尾不要多斜杠。再验证 Vault 读写。在宿主机data/vault下建个测试文件echo # 测试笔记 data/vault/test-sync.md docker compose exec api ls -la /data/vault/应该能看到test-sync.md。然后触发一次同步curl -X POST http://localhost:8080/api/v1/sync/trigger \ -H Authorization: Bearer $API_SECRET_KEY返回{status:success,files_processed:1}就说明文件监听和索引链路通了。最后打开浏览器访问http://你的服务器IP:3000应该能看到 Hermes 的 Web 界面首次访问会让你创建管理员账号。创建完登录进去在 Vault 列表里能看到刚才的测试文件整个部署就算验证通过了。5. 本篇常见报错排查部署过程中最容易撞上的几个报错我按出现频率排一下。401 Unauthorized。这个分两种一种是调 Hermes 自己的 API 时 401说明API_SECRET_KEY和请求头里的 Bearer 不一致检查.env和 curl 命令另一种是 Hermes 调 TaoToken 时 401说明OPENAI_API_KEY填错了或者过期了去 API Key 管理页重新生成一个。注意.env改完要docker compose up -d重建容器才生效光restart不会重新读 env_file。local proxy failed / connection refused。这个报错通常出现在 API 容器启动时日志里会写dial tcp 127.0.0.1:5432: connect: connection refused。原因是POSTGRES_HOST被写成了localhost或127.0.0.1。容器里的 localhost 是容器自己不是宿主机更不是 postgres 容器。正确值应该是postgres也就是 compose 里的服务名。同理 Redis 的 host 是redis。如果你在.env里写了POSTGRES_HOSTlocalhostcompose 的environment段会覆盖它但如果你把覆盖那段删了就会踩坑。reading choices: unexpected end of JSON input。这个报错来自模型调用层意思是 TaoToken 返回的响应体不是合法 JSON。常见原因有三个Base URL 写成了https://taotoken.net/api但漏了/v1导致请求打到了错误路径Model ID 填了一个不存在的模型网关返回了 HTML 错误页或者请求体里max_tokens设得过大超过了模型上限。排查方法是在宿主机直接 curl 一下 TaoTokencurl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $OPENAI_API_KEY \ -H Content-Type: application/json \ -d {model:gpt-4o,messages:[{role:user,content:hi}]}如果这条能通说明 Key 和 Base URL 没问题问题在 Hermes 的配置映射上。OAuth / token refresh failed。如果你用的是 Anthropic 风格接入Hermes 可能会尝试走 OAuth 流程但 TaoToken 走的是 API Key 模式不需要 OAuth。解决办法是在.env里显式设置ANTHROPIC_AUTH_MODEapi_key或者干脆只用 OpenAI 兼容模式把LLM_PROVIDER设成openai。这样 Hermes 不会去尝试 OAuth 刷新。Vault 权限拒绝。日志里出现permission denied或EACCES是因为容器内进程的 UID 和宿主机目录所有者不匹配。先查容器用户docker compose exec api id假设输出uid1000(hermes) gid1000(hermes)那就把宿主机 Vault 目录改成这个属主sudo chown -R 1000:1000 data/vault chmod -R 755 data/vault如果你在 macOS 或 WSL2 上跑文件系统权限映射比较特殊直接chmod -R 777 data/vault也能绕过但生产环境不建议这么干。容器反复重启。docker compose ps看到Restarting状态先看日志docker compose logs --tail 100 api如果是OOMKilled说明内存不够在 compose 里给 api 加deploy.resources.limits.memory: 2G。如果是exit code 1且日志只有一行多半是.env里有语法错误比如值里带了空格没加引号。6. 长期运行与后续接入建议部署跑通只是起点真正让第二大脑产生价值的是它持续在后台工作。这里给几个实操建议。第一把备份做成定时任务。Vault 是你的核心资产PostgreSQL 里存的是索引和元数据两者都要备。写个脚本每天凌晨跑#!/bin/bash DATE$(date %Y%m%d) docker compose exec -T postgres pg_dump -U hermes hermes | gzip data/backups/db_$DATE.sql.gz tar -czf data/backups/vault_$DATE.tar.gz -C data vault find data/backups -mtime 30 -delete加到 crontab0 3 * * * ~/hermes-agent/backup.sh。第二日志要轮转。LOG_FORMATjson配合 Docker 的日志驱动在 compose 里给每个服务加logging: driver: json-file options: max-size: 50m max-file: 5不然跑几个月日志能把磁盘撑满。第三模型通道的长期策略。如果你只是偶尔用按量计费没问题但如果 Hermes 要跑定时摘要、自动打标签这类持续任务建议看一下 Coding Plan额度模型更适合这种场景入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的示例如果你要自己写脚本调模型可以参考。第四后续扩展方向。当前是单机 compose 部署如果 Vault 变大、索引变慢可以先把 PostgreSQL 的shared_buffers调大或者把 Worker 的WORKER_CONCURRENCY从 5 提到 10。再往上就是多副本 Nginx 负载均衡不过对个人第二大脑来说单机跑个一两年完全够用。真要上 K8s那是团队协作场景才需要考虑的事。最后提醒一句.env文件千万别提交到 Git.gitignore里加上.env、data/、logs/。我见过有人把带 Key 的配置推到公开仓库结果被扫到后额度被刷光。部署这件事稳比快重要配置一次跑通后面就是让它安静地在后台干活。