Docker Compose #01|QQ Bot All-in-One

Lucian 202 发布于 2 小时前 14 次阅读


这是「Docker Compose」系列的第一篇。我打算把自己在跑、用着舒服的那些 Compose 部署慢慢分享出来。第一篇就从群里那个 QQ 机器人开始。

目前正在群里跑的 QQ 机器人,背后是这么一套东西:机器人框架(AstrBot)+ QQ 接入(PMHQ / LLBot)+ 代码沙盒(Docker Socket Proxy / Shipyard)+ 订阅推送(nonebot-bison / we-mp-rss

⚠️ 下面所有密钥、IP、QQ 号都换成了占位符,真实值请放进自己的 .env,不要直接提交/公开。

# ============================================================
# QQ Bot All-in-One — AstrBot 全栈编排
# 提示:Compose V2 已不需要顶部 version 字段,故删除
# 提示:敏感值统一用 ${VAR} 占位,真实值写进同目录 .env
# ============================================================

services:

  # ──────────────────────────────────────────────
  # 🛡️ 安保组|Docker Socket 门神
  # 沙盒功能需要操作 Docker,但绝不把整个 docker.sock 直接交给上层。
  # 用代理只放行「建/销毁容器」这类最小权限,挡住提权与横向移动。
  # ──────────────────────────────────────────────
  dockerproxy:
    image: tecnativa/docker-socket-proxy:latest
    container_name: docker-proxy
    restart: always
    privileged: false              # 代理本身不需要特权
    environment:
      - CONTAINERS=1   # 允许创建/管理沙盒容器
      - NETWORKS=1     # 允许为沙盒分配网络
      - IMAGES=1       # 允许拉取沙盒镜像
      - POST=1         # 允许写类请求(创建等)
      - DELETE=1       # 允许销毁跑完的废弃沙盒
      - EXEC=0         # ❌ 禁止进入其它容器执行命令(防横向移动)
      - VOLUMES=1      # 允许操作数据卷
      - SYSTEM=0       # ❌ 禁止系统级接管
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro   # 只读挂载宿主 socket
    networks:
      - astrbot_network

  # ──────────────────────────────────────────────
  # ⚙️ 沙盒组|Shipyard Neo Bay(沙盒控制面)
  # 机器人要执行代码时,由它去(经上面的代理)创建/回收一次性沙盒容器。
  # ──────────────────────────────────────────────
  shipyard-bay:
    image: shipyard-neo-bay:latest
    container_name: shipyard-bay
    restart: always
    environment:
      - BAY_API_KEY=${BAY_API_KEY}   # 控制面鉴权密钥 —— 放进 .env,切勿明文
    volumes:
      - /mnt/data/docker_data/astrbot/data/shipyard/bay_data:/app/data
      - /mnt/data/docker_data/astrbot/data/shipyard/cargos:/var/lib/bay/cargos      # 沙盒产物/缓存
      - /mnt/data/docker_data/astrbot/data/shipyard-neo/config.yaml:/app/config.yaml:ro
    networks:
      - astrbot_network

  # ──────────────────────────────────────────────
  # 🤖 核心组|AstrBot(机器人大脑 / 网关)
  # 消息路由、插件、LLM 调用都在这;唯一对外开放 WebUI 端口。
  # ──────────────────────────────────────────────
  astrbot:
    image: soulter/astrbot:latest
    container_name: astrbot
    restart: always
    ports:
      - "6185:6185"                  # WebUI(公网机建议绑 127.0.0.1 或走反代)
    volumes:
      - /mnt/data/docker_data/astrbot/data:/AstrBot/data
    networks:
      - astrbot_network
      - ragflow                      # 额外接 RAGFlow 知识库网络
    environment:
      - TZ=Asia/Taipei

  # ──────────────────────────────────────────────
  # 💬 QQ 接入组(一)|PMHQ(NTQQ 进程层)
  # 注意:这里用了 privileged —— 与上面的最小权限隔离是反例,
  # 但 NTQQ 运行需要,属于「明知风险、单点破例」,已用内部网络收口。
  # ──────────────────────────────────────────────
  pmhq:
    image: linyuchen/pmhq:latest
    container_name: pmhq
    privileged: true                 # ⚠️ NTQQ 需要;仅此一处破例
    restart: unless-stopped
    environment:
      - ENABLE_HEADLESS=false        # 有头更稳定;想省内存可设 true
      - AUTO_LOGIN_QQ=${AUTO_LOGIN_QQ:-}   # 自动登录的 QQ 号,留空则手动扫码
    volumes:
      - /mnt/data/docker_data/astrbot/llbot/QQ:/root/.config/QQ        # QQ 登录态/数据
      - /mnt/data/docker_data/astrbot/llbot/config:/app/llbot/data:rw
      - /mnt/data/docker_data/astrbot/data:/AstrBot/data
    networks:
      - astrbot_network
    healthcheck:                     # 探测 NTQQ 进程是否就绪
      test: ["CMD", "curl", "-f", "http://localhost:13000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # ──────────────────────────────────────────────
  # 💬 QQ 接入组(二)|LLBot(OneBot/协议层,替代 NapCat)
  # 把 PMHQ 的 NTQQ 包装成 OneBot 协议,再喂给 AstrBot。
  # ──────────────────────────────────────────────
  llbot:
    image: linyuchen/llbot:latest
    container_name: llbot
    restart: unless-stopped
    extra_hosts:
      - "host.docker.internal:host-gateway"   # 容器内回连宿主机
    environment:
      - PMHQ_HOST=pmhq               # 指向上面的 NTQQ 进程层
      - WEBUI_PORT=3080
    ports:
      - "3080:3080"                  # WebUI
      - "3001:3001"                  # OneBot 11 WebSocket(按协议配置增删)
    volumes:
      - /mnt/data/docker_data/astrbot/llbot/QQ:/root/.config/QQ
      - /mnt/data/docker_data/astrbot/llbot/config:/app/llbot/data:rw
      - /mnt/data/docker_data/astrbot/data:/AstrBot/data
    networks:
      - astrbot_network
    depends_on:
      - pmhq                         # 想更稳可改 condition: service_healthy
    healthcheck:                     # 探测 node 进程是否存活
      test: ["CMD", "sh", "-c", "ps | grep '[n]ode'"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # ──────────────────────────────────────────────
  # 📡 推送组(一)|nonebot-bison(多平台订阅推送,替换 elf-rss)
  # 订阅各平台动态并推到群里;这里用 nobrowser 镜像、关掉截图省资源。
  # ──────────────────────────────────────────────
  bison:
    image: felinae98/nonebot-bison:v0.9.14-nobrowser
    container_name: bison
    restart: always
    networks:
      - astrbot_network
    ports:
      - "8088:8080"                  # 管理后台
    volumes:
      - /mnt/data/docker_data/astrbot/bison/data:/data
      - /mnt/data/docker_data/astrbot/bison/data/patch_rss_interval.py:/app/extra_plugins/patch_rss_interval.py:ro   # 自定义补丁:调订阅间隔
    environment:
      - TZ=Asia/Taipei
      - HOST=0.0.0.0
      - PORT=8080
      - DRIVER=~fastapi+~aiohttp
      - COMMAND_START=["/", ""]
      - SUPERUSERS=["${BISON_SUPERUSER}"]      # 管理员 QQ —— 用 .env 占位
      - NICKNAME=["bison"]
      - BISON_CONFIG_PATH=/data
      - BISON_OUTER_URL=${BISON_OUTER_URL}     # 对外访问地址 —— 别把真实 IP 写死
      - BISON_TO_ME=true
      - BISON_USE_QUEUE=true        # 推送走队列,削峰
      - BISON_USE_PIC=false         # 纯文本,不渲染图片(配合 nobrowser)
      - BISON_INIT_FILTER=true
      - BISON_FILTER_LOG=false
      - BISON_RESEND_TIMES=2        # 失败重发次数
      - HTMLRENDER_CI_MODE=true
    depends_on:
      - llbot

  # ──────────────────────────────────────────────
  # 📡 推送组(二)|we-mp-rss(微信公众号转 RSS)
  # ──────────────────────────────────────────────
  we-mp-rss:
    image: ghcr.io/rachelos/we-mp-rss:latest
    container_name: we-mp-rss
    restart: always
    networks:
      - astrbot_network
    ports:
      - "8001:8001"
    volumes:
      - /mnt/data/docker_data/astrbot/we-mp-rss/data:/app/data
    environment:
      - TZ=Asia/Taipei
      - TOKEN_EXPIRE_MINUTES=5256000        # token 有效期(约 10 年)
      - DB=sqlite:////app/data/db.db
      - ENABLE_JOB=True                     # 开启定时抓取
      - SPAN_INTERVAL=15                    # 抓取间隔(分钟)
      - GATHER.CONTENT=False                # 默认只抓标题/链接,不抓正文
      - MAX_PAGE=5
      - GATHER.CONTENT_AUTO_CHECK=True
      - GATHER.CONTENT_AUTO_INTERVAL=5
      - THREADS=4
      - CUSTOM_WEBHOOK=${WE_MP_RSS_WEBHOOK} # ⚠️ 原值含账号密码,务必放 .env
      - SEND_CODE=True                      # 授权过期时推送扫码提醒
      - CODE_TITLE=WeRSS授权过期,请重新扫码
      - LOG_FILE=/app/data/we-mp-rss.log
      - LOG_LEVEL=DEBUG                     # 上线后建议降到 INFO

# 两个网络都设为 external —— 需提前 docker network create
networks:
  astrbot_network:
    external: true
  ragflow:
    external: true

整套跑起来后,群里那只机器人就同时拥有了:聊天 / 跑代码(带沙盒)/ 多平台订阅推送 三件套。具体如何部署,参见Docker相关技术,我用的是Portainer可视化面板,也推荐尝试。