Docker 部署
在 Linux 上用官方 Docker 镜像运行 SnowLuma
SnowLuma 在 Linux 上只通过 Docker 运行。镜像内置 Linux QQ + Xvfb + VNC + noVNC + supervisord,开箱即用;宿主机只需要 Docker。
镜像构建脚本与 Compose 模板在独立仓库 SnowLuma/SnowLuma.Docker.Framework。
镜像信息
| 项 | 值 |
|---|---|
| 镜像 | motricseven7/snowluma:latest (或具体 tag :v1.8.0) |
| 架构 | linux/amd64、linux/arm64 |
| 基础 | node:22-bookworm-slim |
| 内置 | Linux QQ、Xvfb、VNC、noVNC、supervisord、SnowLuma lite 发行包 |
镜像不在容器内重新编译 SnowLuma —— 它消费主仓 GitHub Release 的预编译 lite tarball,所以发版与镜像构建解耦。
端口
| 端口 | 用途 |
|---|---|
5900 | VNC(远程桌面,密码登录 QQ 用) |
6081 | noVNC(浏览器版 VNC,无客户端访问) |
5099 | SnowLuma WebUI |
3000 | OneBot HTTP(默认) |
3001 | OneBot WebSocket(默认) |
宿主机准备:放开 ptrace 限制
这一步必须先做,否则容器起来后 hook 注入会失败。
SnowLuma 的 native addon 通过 ptrace 把 hook 注入到容器内的 QQ 进程。现代 Linux 发行版(Ubuntu / Debian / Arch 等)默认把 kernel.yama.ptrace_scope 设为 1 或更高,即使容器启动加了 --cap-add=SYS_PTRACE,注入也会被宿主内核拦掉。
在宿主机上只需要执行一次:
echo 'kernel.yama.ptrace_scope=0' | sudo tee /etc/sysctl.d/99-snowluma-ptrace.conf
sudo sysctl --system验证是否生效:
sysctl kernel.yama.ptrace_scope
# 期望输出:kernel.yama.ptrace_scope = 0写到 /etc/sysctl.d/99-snowluma-ptrace.conf 是为了让设置在重启后自动恢复。如果你的环境对宿主机安全有更严格要求(比如多租户、面向公网),请考虑使用受信任的隔离机制(独立物理机 / VM)替代这一步。
一行 docker run
docker run -d \
--name snowluma \
--restart unless-stopped \
--shm-size=1g \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
-e VNC_PASSWD=vncpasswd \
-p 5900:5900 \
-p 6081:6081 \
-p 5099:5099 \
-p 3000:3000 \
-p 3001:3001 \
-v snowluma-data:/app/snowluma-data \
-v snowluma-qq-config:/app/.config \
-v snowluma-qq-data:/app/.local/share \
motricseven7/snowluma:latest--cap-add=SYS_PTRACE 和 --security-opt seccomp=unconfined 不能省。SnowLuma 的 native addon 通过 ptrace 把 hook 注入 QQ 进程,默认 seccomp profile 会拦截。
--shm-size=1g 是 QQ(基于 Chromium)的运行要求,容器默认 /dev/shm 太小会崩。
docker-compose
更推荐用 Compose。仓库 docker-compose.yml:
services:
snowluma:
image: ${SNOWLUMA_IMAGE:-motricseven7/snowluma:latest}
container_name: ${SNOWLUMA_CONTAINER:-snowluma}
restart: unless-stopped
shm_size: 1gb
cap_add:
- SYS_PTRACE
security_opt:
- seccomp=unconfined
environment:
VNC_PASSWD: ${VNC_PASSWD:-vncpasswd}
SNOWLUMA_UID: ${SNOWLUMA_UID:-1000}
SNOWLUMA_GID: ${SNOWLUMA_GID:-1000}
SNOWLUMA_WEBUI_PORT: ${SNOWLUMA_WEBUI_PORT:-5099}
SNOWLUMA_LOG_LEVEL: ${SNOWLUMA_LOG_LEVEL:-info}
SNOWLUMA_SCREEN: ${SNOWLUMA_SCREEN:-1920x1080x16}
SNOWLUMA_HOOK_AUTOLOAD: ${SNOWLUMA_HOOK_AUTOLOAD:-1}
ports:
- "${VNC_PORT:-5900}:5900"
- "${NOVNC_PORT:-6081}:6081"
- "${SNOWLUMA_WEBUI_HOST_PORT:-5099}:${SNOWLUMA_WEBUI_PORT:-5099}"
- "${ONEBOT_HTTP_PORT:-3000}:3000"
- "${ONEBOT_WS_PORT:-3001}:3001"
volumes:
- snowluma-data:/app/snowluma-data
- snowluma-qq-config:/app/.config
- snowluma-qq-data:/app/.local/share
volumes:
snowluma-data:
snowluma-qq-config:
snowluma-qq-data:启动:
docker compose up -d升级镜像:
docker compose pull
docker compose up -d环境变量
| 变量 | 默认 | 说明 |
|---|---|---|
VNC_PASSWD | vncpasswd | VNC / noVNC 登录密码。改成自己的,不然别人也能进容器里看你 QQ。 |
SNOWLUMA_UID | 1000 | 容器内 snowluma 用户的 uid。和你挂载卷的宿主属主匹配。 |
SNOWLUMA_GID | 1000 | 容器内 snowluma 用户的 gid。 |
SNOWLUMA_WEBUI_PORT | 5099 | WebUI 监听端口(容器内)。 |
SNOWLUMA_LOG_LEVEL | info | error / warn / info / debug。 |
SNOWLUMA_SCREEN | 1920x1080x16 | Xvfb 分辨率 + 色深。 |
SNOWLUMA_HOOK_AUTOLOAD | 1 | 镜像默认开自动注入。设 0 切回手动 Load 流程。 |
环境变量优先级高于 runtime.json,所以 Docker 用环境变量覆盖最方便。
数据卷
| 卷 | 容器路径 | 内容 |
|---|---|---|
snowluma-data | /app/snowluma-data | SnowLuma 配置、缓存、SDB 等。config/onebot.json 和 config/runtime.json 都在这里。 |
snowluma-qq-config | /app/.config | QQ 客户端配置。 |
snowluma-qq-data | /app/.local/share | QQ 用户数据(登录态、缓存等)。 |
升级镜像不要删这三个卷,否则会丢登录态和配置。
首次登录
- 启动容器后用浏览器打开
http://<IP>:6081/(noVNC),输入VNC_PASSWD。 - 在远程桌面里能看到 QQ 已自动起来,扫码登录。
- 登录完成后 hook 会自动从被动观察切到工作模式,无需在 WebUI 里手动 Load。
- 浏览器打开
http://<IP>:5099/访问 SnowLuma WebUI。
找 WebUI 临时密码
首次启动会在日志里输出一次性密码:
docker logs snowluma 2>&1 | grep -E "临时密码|initial credentials" | tail -n 1只要密码字符串:
docker logs snowluma 2>&1 | \
sed -nE 's/.*(临时密码: |initial credentials: user=admin password=)([^[:space:]]+).*/\2/p' | tail -n 1临时密码只在全新数据卷首次启动时输出一次。复用旧卷 / 重启都不会重新生成。
多开 QQ(多账号)
Linux QQ 自带单实例锁:在已有 QQ 跑着的桌面里直接再点一次 QQ 图标,只会把已有窗口聚焦,起不来第二个进程。锁文件落在 $HOME/.config/QQ/SingletonLock,所以解法是给第二个实例一个独立的 HOME。
SnowLuma 一侧不需要任何额外配置:HookManager 会遍历所有 QQ 主进程,新启动一个就自动注入一个,每个 UIN 拉一个独立 OneBotInstance,配置存到对应的 config/onebot_<uin>.json。
在 noVNC 桌面里启第二个 QQ
-
在 noVNC 桌面里打开终端(任务栏右键 / 用
xterm/ 按容器内任意终端快捷键)。 -
用独立
HOME起新 QQ:mkdir -p /app/qq-acct2 HOME=/app/qq-acct2 qq --no-sandbox & disown -
一个新 QQ 登录窗口会出现,扫码 / 账密登录另一个号即可。
-
登录完成后 SnowLuma 自动注入并按新 UIN 起一个 OneBot 实例。
disown 让 QQ 与终端解绑 —— 关掉终端窗口 QQ 不会跟着退出。
让它持久化
上面那条命令里 /app/qq-acct2 是容器内路径,容器重建会丢。要持久化,在 docker-compose.yml 给每个账号加独立卷:
services:
snowluma:
# ... 原有配置 ...
volumes:
- snowluma-data:/app/snowluma-data
- snowluma-qq-config:/app/.config # 主账号
- snowluma-qq-data:/app/.local/share # 主账号
- snowluma-qq2:/app/qq-acct2 # 第二个账号的完整 HOME
- snowluma-qq3:/app/qq-acct3 # 第三个账号 ...
volumes:
snowluma-data:
snowluma-qq-config:
snowluma-qq-data:
snowluma-qq2:
snowluma-qq3:之后每次需要拉起第二个号,在 noVNC 终端跑:
HOME=/app/qq-acct2 qq --no-sandbox &
disown开机自动拉所有账号
把上面的命令写成一个 shell 脚本,放进持久卷 /app/snowluma-data/start-extra-qq.sh:
#!/bin/sh
for home in /app/qq-acct2 /app/qq-acct3; do
[ -d "$home" ] || continue
HOME="$home" qq --no-sandbox >/dev/null 2>&1 &
done然后在 noVNC 桌面进系统的开机自启目录(XFCE 是 ~/.config/autostart/)放一个 desktop 入口指向这个脚本。或者更稳的:把脚本加到 supervisord.conf 作为新的 program。后者需要重建镜像,前者重启容器就生效。
限制 & 注意
- 每个 QQ 实例独占自己的
HOME—— 别让两个 QQ 共用同一个HOME,会冲突。 - 资源:每个 QQ 进程跑 Chromium 大概吃 300-500MB 内存,
--shm-size主账号 1GB 够用,多开建议加到2gb或更高。 - noVNC 同屏可见:多个 QQ 窗口都在同一个 Xvfb 桌面里,VNC 进去可以分别操作。如果只想登一次就走,登完后把 QQ 窗口最小化即可,不会影响 hook。
- WebUI 自动列出所有 UIN:SnowLuma 不需要单独配置就能识别新登录的账号,WebUI /
get_login_info都会出现新 UIN。
自动注入
镜像默认开 SNOWLUMA_HOOK_AUTOLOAD=1:容器一启动就把 hook 注入 QQ 主进程(被动观察),扫码登录完成后 hook 自动切到工作模式。supervisor 让 QQ 崩溃自动重启时也走同样流程。
关闭自动注入
想保留传统的"WebUI 里手动 Load"流程:
docker run -e SNOWLUMA_HOOK_AUTOLOAD=0 ... motricseven7/snowluma:latest或在 docker-compose.yml 改 SNOWLUMA_HOOK_AUTOLOAD: 0,或编辑持久卷里的 /app/snowluma-data/config/runtime.json 设 "hookAutoLoad": false。环境变量优先。
常用运维
进容器:
docker exec -it snowluma bash查日志:
docker logs -f snowluma只看 SnowLuma 主进程日志(过滤掉 QQ 自己的输出):
docker logs -f snowluma 2>&1 | grep -v "QtNetwork\|GLib\|libQt6"进入 SnowLuma 数据目录:
docker exec -it snowluma bash -c "cd /app/snowluma-data && ls -la"修改 OneBot 配置:编辑 /app/snowluma-data/config/onebot_<uin>.json,重启容器:
docker restart snowluma端口冲突
宿主机端口被占用时,调宿主侧映射即可,容器内端口不变。例:把 OneBot HTTP 改为宿主的 8080:
ports:
- "8080:3000" # 宿主 8080 → 容器 3000或通过环境变量(Compose 模板已支持):
ONEBOT_HTTP_PORT=8080 docker compose up -d自己构建镜像
如果想用未发布的 SnowLuma 版本(比如本地分支):
# 在 SnowLuma 主仓本地构建出 lite tarball
pnpm build:all
# 把 SnowLuma-<TAG>-linux-x64-lite.tar.gz 放到 Docker 仓根目录并重命名
cp dist/SnowLuma-*-linux-x64-lite.tar.gz \
../SnowLuma.Docker.Framework/SnowLuma.Framework.tar.gz
cd ../SnowLuma.Docker.Framework
./scripts/build-image.sh或者直接让脚本从 release 拉:
SNOWLUMA_TAG=v1.8.0 ./scripts/build-image.sh多架构 manifest 合并请走 CI(.github/workflows/docker-image.yml);本地脚本只构建单平台。
安全提示
- SnowLuma 用 native addon 把代码注入 QQ 进程;容器启动需要
SYS_PTRACE能力和seccomp=unconfined。仅在受信任的宿主上运行。 - VNC / noVNC 默认密码
vncpasswd务必改掉,否则你的 QQ 桌面在公网上裸奔。 - 请遵守 NTQQ / Tencent 的使用许可,以及 SnowLuma 与依赖项的开源协议。