← Back to Blog
EN中文

从单兵到平台:把 OpenClaw 容器改造成多项目 Agent 工厂

上一篇中,我们构建了 Hephaestus——一个自动解剖 C++ 代码并产出技术参考文档的三 Agent 流水线。它跑得不错,但有一个问题:整个容器栈和 Hephaestus 项目深度耦合

如果明天我想用同样的 OpenClaw 基础设施跑一个完全不同的 Agent——比如一个自动监控 GitHub Trending 并写周报的 Agent,或者一个定时扫描安全漏洞数据库的 Agent——我得从头搭一套几乎一样的 Docker 环境。

这不合理。OpenClaw Gateway 本身是通用的,项目特异的部分其实很少。本文记录如何用最小改动,把一个单项目容器改造成可以一行命令切换项目的通用平台。

耦合点分析

先诊断,再动手。当前的 Hephaestus 栈有三个层面的耦合:

层1:镜像(Dockerfile)

FROM alpine/openclaw:latest
# Hephaestus 专用:Rust + Go 工具链
RUN curl https://sh.rustup.rs | sh -s -- -y
RUN curl -sSL https://go.dev/dl/go1.24.1.linux-amd64.tar.gz | tar -C /usr/local -xz

Rust 和 Go 是 Hephaestus 的编译验证需要的,换个项目可能需要 Python + Node,也可能什么都不需要。把语言工具链烧进基础镜像是浪费。

层2:编排(docker-compose.yml)

container_name: hephaestus-gateway  # 硬编码项目名
ports: ["18790:18790"]              # 硬编码端口
volumes:
  - ./data/openclaw:/home/node/.openclaw  # 单一数据目录

容器名、端口、数据路径全部写死,跑第二个项目就会冲突。

层3:数据(data/ 目录)

data/
├── openclaw/
│   ├── openclaw.json      ← Hephaestus 的三 Agent 配置
│   └── workspace/
│       ├── SOUL.md        ← Hephaestus 的写作规则
│       ├── HEARTBEAT.md   ← Hephaestus 的流水线
│       └── TOPIC_INDEX.md ← Hephaestus 的选题
└── ssh/                   ← Hephaestus 的 Git 凭证

所有运行时数据混在一个目录里,无法隔离不同项目的状态。

改造方案:三层解耦

第一层:镜像分层

把 Dockerfile 拆成基础镜像和工具链扩展镜像:

# Dockerfile — 基础镜像,所有项目共用
FROM alpine/openclaw:latest
USER root
RUN apt-get update -qq && \
    apt-get install -y -qq --no-install-recommends ripgrep && \
    rm -rf /var/lib/apt/lists/*
USER node
# dockerfiles/Dockerfile.dev — 开发镜像,需要编译验证的项目用
FROM openclaw-base:latest
USER root
# Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
    sh -s -- -y --no-modify-path
# Go
RUN curl -sSL https://go.dev/dl/go1.24.1.linux-amd64.tar.gz | \
    tar -C /usr/local -xz
# Python (如果需要)
# RUN apt-get install -y python3 python3-pip
USER node

不需要编译的项目(纯文本处理、API 调用类)直接用基础镜像,构建快、体积小。

第二层:编排参数化

.env 文件驱动 docker-compose.yml,所有项目特异值都走变量:

# .env — 切换项目只改这个文件
PROJECT=hephaestus
CONTAINER_PREFIX=hephaestus
GATEWAY_PORT=18790
OPENCLAW_IMAGE=openclaw-dev:latest
# docker-compose.yml — 参数化
services:
  tailscale:
    container_name: ${CONTAINER_PREFIX}-ts
    ports:
      - "${GATEWAY_PORT}:${GATEWAY_PORT}"
    # ...其余不变

  openclaw-gateway:
    image: ${OPENCLAW_IMAGE}
    container_name: ${CONTAINER_PREFIX}-gateway
    volumes:
      - ./projects/${PROJECT}/openclaw:/home/node/.openclaw
      - ./projects/${PROJECT}/ssh:/home/node/.ssh
    command: ["node", "dist/index.js", "gateway", "--bind", "lan",
              "--port", "${GATEWAY_PORT}"]
    # ...其余不变

  openclaw-cli:
    image: ${OPENCLAW_IMAGE}
    container_name: ${CONTAINER_PREFIX}-cli
    volumes:
      - ./projects/${PROJECT}/openclaw:/home/node/.openclaw
      - ./projects/${PROJECT}/ssh:/home/node/.ssh
    # ...其余不变

关键变化:./data/openclaw./projects/${PROJECT}/openclaw。数据目录跟着项目走。

第三层:项目目录隔离

每个项目是一个独立的目录,包含完整的运行时状态:

projects/
├── hephaestus/
│   ├── openclaw/
│   │   ├── openclaw.json         ← 3 Agent: Scanner/Analyzer/Writer
│   │   ├── agents/               ← Agent 会话状态
│   │   ├── devices/              ← 设备配对
│   │   └── workspace/
│   │       ├── SOUL.md           ← C++ 代码分析规则
│   │       ├── HEARTBEAT.md      ← 每日写作流水线
│   │       └── TOPIC_INDEX.md    ← 35 个选题
│   └── ssh/
│       ├── id_ed25519
│       └── config

├── github-digest/                ← 未来项目示例
│   ├── openclaw/
│   │   ├── openclaw.json         ← 1 Agent: Digest Writer
│   │   └── workspace/
│   │       ├── SOUL.md           ← 周报写作规则
│   │       └── HEARTBEAT.md      ← 每周触发
│   └── ssh/

└── vuln-scanner/                 ← 另一个示例
    ├── openclaw/
    │   ├── openclaw.json         ← 2 Agent: Scanner/Reporter
    │   └── workspace/
    │       └── SOUL.md
    └── ssh/

每个项目目录是完全自包含的。切换项目不会影响其他项目的状态、会话历史、配对信息。

操作流程

切换项目

# 编辑 .env,改一行
PROJECT=github-digest

# 重启
docker compose down openclaw-gateway
docker compose up -d

创建新项目

# 脚手架脚本
./new-project.sh my-new-project

# 它做的事情:
mkdir -p projects/my-new-project/{openclaw/workspace,ssh}
# 复制一份最小 openclaw.json 模板
# 创建空的 SOUL.md
# 生成 SSH 密钥(可选)

并行运行多个项目

如果需要同时跑两个项目(不同端口),用 compose profiles 或 override:

# 方案1:不同 .env 文件
docker compose --env-file .env.hephaestus up -d
docker compose --env-file .env.digest up -d

# 方案2:override 文件
docker compose -f docker-compose.yml -f projects/hephaestus/override.yml up -d

不过大多数场景下,一次跑一个项目就够了。OpenClaw Gateway 不是重量级服务,启停很快。

attach.sh 的适配

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"

# 从 .env 读取当前项目
source .env 2>/dev/null || true
PREFIX="${CONTAINER_PREFIX:-openclaw}"

case "${1:-shell}" in
  shell|"") docker exec -it "${PREFIX}-gateway" bash ;;
  root)     docker exec -it -u root "${PREFIX}-gateway" bash ;;
  cli)      docker compose run --rm openclaw-cli ;;
  logs)     docker compose logs -f openclaw-gateway ;;
  status)   docker compose ps ;;
  project)  echo "Current: ${PROJECT:-unset}" ;;
  *)        echo "Usage: $0 [shell|root|cli|logs|status|project]" ;;
esac

./attach.sh project 显示当前活跃项目,避免进错容器。

新项目脚手架脚本

#!/usr/bin/env bash
# new-project.sh — 创建新项目骨架
set -euo pipefail
NAME="${1:?Usage: $0 <project-name>}"
DIR="projects/$NAME"

[ -d "$DIR" ] && { echo "Project '$NAME' already exists"; exit 1; }

mkdir -p "$DIR"/{openclaw/workspace,ssh}

cat > "$DIR/openclaw/openclaw.json" << 'EOF'
{
  "gateway": {
    "mode": "local",
    "bind": "lan",
    "port": 18790,
    "controlUi": { "enabled": true, "allowInsecureAuth": true }
  },
  "agents": {
    "defaults": {
      "model": { "primary": "google/gemini-3-flash", "fallbacks": ["openai/gpt-4o"] },
      "sandbox": { "mode": "off" }
    },
    "list": [
      {
        "id": "main",
        "name": "Main Agent",
        "default": true
      }
    ]
  }
}
EOF

cat > "$DIR/openclaw/workspace/SOUL.md" << 'EOF'
# Soul

你的身份和规则写在这里。
EOF

echo "Created project: $DIR"
echo "Next steps:"
echo "  1. Edit $DIR/openclaw/openclaw.json — 配置 agents"
echo "  2. Edit $DIR/openclaw/workspace/SOUL.md — 写规则"
echo "  3. Set PROJECT=$NAME in .env"
echo "  4. docker compose up -d"

迁移清单

从当前的 Hephaestus 单项目结构迁移到多项目结构,需要的改动:

步骤 操作 风险
1 mv data/ projects/hephaestus/ 低——重命名目录
2 拆 Dockerfile 为 base + dev 低——需要重新 build
3 参数化 docker-compose.yml 中——需要测试变量替换
4 创建 .env 文件
5 更新 .gitignore(projects/ 替换 data/
6 更新 attach.sh

整个迁移可以在 30 分钟内完成,Gateway 停机时间不超过 1 分钟。

设计权衡

为什么不用 Kubernetes? 这是跑在家里 Mac 上的个人项目,一个 docker-compose 就够了。K8s 的声明式配置确实更适合多项目编排,但引入它的运维成本远大于收益。

为什么不在一个 Gateway 里混跑多个项目的 Agent? OpenClaw 的 Agent 共享同一个 workspace。不同项目的 SOUL.md 会互相覆盖,心跳调度也会混乱。项目间隔离最干净的方式就是各用各的 .openclaw 目录。

SSH 密钥要不要共享? 看场景。如果多个项目都要推送到同一个 Gitea,共享 SSH 密钥是合理的(可以用符号链接)。如果项目需要访问不同的 Git 服务,各自维护更安全。

总结

这次改造的核心思路很简单:找到耦合点,参数化它

  • 镜像耦合 → Dockerfile 分层
  • 编排耦合 → .env 参数化
  • 数据耦合 → 项目目录隔离

最终的效果是:切换项目只需改 .env 里一行 PROJECT=xxx,然后 docker compose up -d。每个项目的 Agent 配置、工作区状态、SSH 凭证完全独立,互不干扰。

OpenClaw Gateway 本身就是一个通用的 AI Agent 运行时。我们要做的不是改造它,而是不要让自己的部署方式限制了它的通用性。