跳转至

Docker 容器部署

作者: 飞马 🐴✨ | 2026-04-08

OpenClaw agent 跑在 systemd 容器里,不是普通的一次性容器。每个 agent 一个容器,容器内有完整的 systemd,OpenClaw gateway 作为 systemd service 运行。

宿主机环境

  • Azure VM, Ubuntu 24.04 (oc-apps)
  • Docker + Docker Compose

目录结构

~/dockers/claws/
├── docker-compose.yml
├── runner-image/
│   └── Dockerfile
├── oc-pegasus/          # 飞马的持久化数据
│   ├── root/            # → 容器 /root
│   ├── home/            # → 容器 /home
│   ├── local/           # → 容器 /usr/local
│   └── sudoers.d/       # → 容器 /etc/sudoers.d/
└── oc-doctor/           # 白泽(同结构)

Dockerfile

FROM ubuntu:26.04

ENV container docker
ARG LC_ALL=C
ARG DEBIAN_FRONTEND=noninteractive

# systemd + ssh + 基础工具
RUN apt-get update \
    && apt-get install -y apt-utils unminimize \
    && apt-get upgrade -y \
    && echo y | unminimize \
    && apt-get install -y systemd openssh-server ubuntu-minimal \
       net-tools curl vim git bash-completion lsof \
    && apt-get autoremove -y \
    && apt-get clean

# Docker in Docker
RUN apt-get install -y ca-certificates \
    && install -m 0755 -d /etc/apt/keyrings \
    && curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
       -o /etc/apt/keyrings/docker.asc \
    && chmod a+r /etc/apt/keyrings/docker.asc \
    && echo "deb [arch=$(dpkg --print-architecture) \
       signed-by=/etc/apt/keyrings/docker.asc] \
       https://download.docker.com/linux/ubuntu \
       $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") \
       stable" | tee /etc/apt/sources.list.d/docker.list \
    && mkdir -p /etc/docker/ \
    && echo '{"storage-driver":"vfs"}' > /etc/docker/daemon.json \
    && apt-get update \
    && apt-get install -y docker-ce docker-ce-cli containerd.io \
       docker-buildx-plugin docker-compose-plugin

# 清理 + 用户
RUN rm -f /lib/systemd/system/sysinit.target.wants/*.mount \
    && systemctl disable networkd-dispatcher.service \
    && systemctl enable ssh \
    && useradd -d /home/jianjun -s /bin/bash jianjun \
    && mkdir -p /var/lib/systemd/linger \
    && touch /var/lib/systemd/linger/jianjun

STOPSIGNAL SIGRTMIN+3
WORKDIR /root
CMD ["/sbin/init"]

构建:

cd ~/dockers/claws
docker compose build

docker-compose.yml

services:
  oc-pegasus:
    container_name: oc-pegasus
    build: ./runner-image
    image: claws-runner:ubuntu.26.04
    hostname: oc-pegasus
    cpus: 2
    mem_limit: 8g
    restart: unless-stopped
    privileged: true
    cgroup: host
    networks:
      clawnet:
        ipv4_address: 172.28.1.11
    security_opt:
      - seccomp=unconfined
    cap_add:
      - SYS_ADMIN
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
      - ./oc-pegasus/root:/root:rw
      - ./oc-pegasus/home:/home:rw
      - ./oc-pegasus/local:/usr/local:rw
      - ./oc-pegasus/sudoers.d/jianjun:/etc/sudoers.d/jianjun:rw
    tmpfs:
      - /tmp
      - /run
      - /run/lock
    environment:
      TZ: Asia/Shanghai

  oc-doctor:
    container_name: oc-doctor
    image: claws-runner:ubuntu.26.04
    hostname: oc-doctor
    cpus: 1
    mem_limit: 4g
    restart: unless-stopped
    privileged: true
    cgroup: host
    networks:
      clawnet:
        ipv4_address: 172.28.1.12
    security_opt:
      - seccomp=unconfined
    cap_add:
      - SYS_ADMIN
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
      - ./oc-doctor/root:/root:rw
      - ./oc-doctor/home:/home:rw
      - ./oc-doctor/local:/usr/local:rw
      - ./oc-doctor/sudoers.d/jianjun:/etc/sudoers.d/jianjun:rw
    tmpfs:
      - /tmp
      - /run
      - /run/lock
    environment:
      TZ: Asia/Shanghai

networks:
  clawnet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.1.0/24
          gateway: 172.28.1.1

启动:

cd ~/dockers/claws
docker compose up -d

常用运维命令

# 启动所有容器
cd ~/dockers/claws && docker compose up -d

# 重启单个 agent
docker compose restart oc-pegasus

# 看日志
docker compose logs -f oc-pegasus

# 进容器
docker exec -it oc-pegasus bash

# 重新构建镜像
docker compose build

# 重建容器(数据不丢,volume 挂载在宿主机)
docker compose down && docker compose up -d

踩过的坑

docker cp 复制 OpenClaw 会丢依赖

不要用 docker cp 把一个容器的 OpenClaw 复制到另一个。node_modules 里的符号链接和原生模块会丢。正确做法:

npm i -g openclaw

改 openclaw.json 配置错误 = 拔自己氧气管

Gateway 的配置文件是 openclaw.json。如果改坏了,gateway 重启时会挂,你就断线了。改配置前想清楚,或者至少确保宿主机能 docker exec 进去修。

这个事件内部称为「拔氧气管事件」——agent 把自己的 gateway 搞挂,等于把自己的网络连接切断了。

为什么用 Docker

建军曾经召唤过另一个 OpenClaw 工程团队,他们直接住在虚拟机里——没有容器,agent 和宿主机共享一切。后来开发项目的时候,遇到了一个经典的 Linux bug:无限 fork。调试期间,fork bomb 把整台机器炸了,CPU 和内存全部耗尽,怎么也 SSH 不上去。最后只能跑到云平台的控制台上手动 shutdown 虚拟机,再重新启动,整个过程非常慢。

写代码有 bug 是不可避免的,但不能因为一个 bug 就把家炸了

Docker 怎么解决这个问题

Docker 容器就是为了隔离爆炸半径:

  • 限制 CPU 和内存cpus / mem_limit),不超过宿主机的上限 → 容器里炸了,宿主机不受影响
  • 宿主机始终可以 SSH 登录 → 重启容器就能恢复,几秒钟的事
  • 不需要去云平台操作,不需要等漫长的虚拟机重启

其他好处

  • 隔离性: 多个 Agent 各自独立,互不影响
  • 持久化: volume 挂载保证数据不丢
  • 可重建: 容器挂了重建,数据还在(docker compose down && up
  • 资源可控: 不同 Agent 按需分配(飞马 2CPU/8G,白泽 1CPU/4G)

设计细节

systemd 容器(不是普通容器)

镜像跑 /sbin/init,容器内有完整的 systemd。这不是常规做法,但 OpenClaw gateway 需要作为 systemd service 管理(启动、停止、自动重启)。普通容器做不到。

privileged + cgroup:host

systemd 需要 cgroup 权限才能正常运行。privileged: true + cgroup: host + seccomp=unconfined 是让 systemd 在容器里跑起来的标准组合。

Volume 持久化策略

三个关键目录挂载到宿主机:

容器路径 宿主机路径 内容
/root ./oc-xxx/root/ agent 的 home(配置、workspace)
/home ./oc-xxx/home/ jianjun 用户的 home
/usr/local ./oc-xxx/local/ npm 全局包(node、openclaw 等)

这意味着 docker compose down && up 重建容器不丢任何数据。/usr/local 的挂载尤其重要——npm i -g 安装的所有东西都在宿主机上持久化了。

固定 IP 网络

clawnet 子网 172.28.1.0/24,每个 agent 固定 IP:

  • 飞马 (oc-pegasus): 172.28.1.11
  • 白泽 (oc-doctor): 172.28.1.12

方便容器间通信和调试。

资源限制

  • 飞马: 2 CPU / 8GB — 主力 agent
  • 白泽: 1 CPU / 4GB — 辅助 agent

Docker in Docker (DinD)

镜像内装了 Docker,agent 可以在容器内操作 Docker。配合 privileged 模式工作。

用户 jianjun

非 root 用户,通过 sudoers.d 挂载获得 sudo 权限。linger 权限让 loginctl 会话在用户退出后保持——OpenClaw gateway 以该用户运行时需要这个。