醒了就干:给 Docker 里的 Misskey 重打镜像,升级Node.js 安全更新

目录

最近 TiDB、PHP 都曝出了大漏洞,这两天刚部署 Misskey,也碰上了 😅

我的 Misskey 应用版本为 2026.10.0,容器内 Node.js 为 26.4.0,重新拉取官方镜像后版本未变。

在原镜像上替换 Node.js,重新打包并部署后,运行时升级至 26.12.1,Misskey 应用版本保持不变。

安全更新

Node.js 官方公告列出了 10 月 21 日的安全更新,最高严重级别为 High。其中包括 DNS 名称含 NUL 字符时被截断、TLS 主机名校验绕过,以及可能导致远程代码执行的 Brotli 解码器越界写入等问题。公告对应的修复版本是:

发布分支本次修复版本
22.x22.23.4
24.x24.22.1
26.x26.12.1

容器使用 26.x,本次升级至同一大版本的 26.12.1。后续升级应重新核对安全公告。

检查容器和上游镜像

Docker 部署的 Misskey 使用镜像内的 Node.js。升级宿主机上的 Node.js,或者单纯重启容器,都不会替换镜像中的运行时。

下面的命令在部署主机执行。示例沿用 /opt/misskey、Compose 服务名 web,以及容器名 misskey-web-1;其他部署需要替换为自己的名称。

1
2
3
4
5
6
7
cd /opt/misskey

docker compose ps
docker compose exec -T web node --version

docker inspect misskey-web-1 \
  --format 'IMAGE={{.Config.Image}} IMAGE_ID={{.Image}}'

检查结果:

1
2
3
Misskey: 2026.10.0
镜像: misskey/misskey:2026.10.0
Node.js: v26.4.0

随后检查上游镜像是否已经更新:

1
2
3
4
5
6
docker pull misskey/misskey:2026.10.0

docker run --rm --network none --entrypoint node \
  misskey/misskey:2026.10.0 --version

docker buildx imagetools inspect misskey/misskey:latest

截至 2026 年 10 月 23 日,官方 2026.10.0 镜像内的 Node.js 仍为 26.4.0,latest 指向同一个镜像索引。上游尚未更新运行时,重新拉取镜像无法完成升级。

保留原镜像,构建修补版本

这次环境是 Linux x86_64,原镜像基于 Debian,使用 glibc。下面下载的 linux-x64 官方二进制适用于这一环境;ARM 或 Alpine/musl 镜像不能直接照搬。

留下配置和镜像回退点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
set -euo pipefail
cd /opt/misskey

STAMP=$(date -u +%Y%m%dT%H%M%SZ)
BACKUP="/opt/misskey/node-security-backup-$STAMP"
mkdir -m 700 "$BACKUP"
cp -a compose.yaml "$BACKUP/compose.yaml"

BASE_IMAGE=$(docker inspect misskey-web-1 --format '{{.Image}}')
docker tag "$BASE_IMAGE" "local/misskey:before-node-security-$STAMP"
docker tag "$BASE_IMAGE" local/misskey:base-node26.4.0

按正在运行的容器镜像 ID 保存本地标签,固定构建基底和回退镜像。此处仅备份 Compose 配置并保留镜像;升级 Misskey 应用或变更数据库前,还需备份数据库和上传文件。

下载并校验官方 Node.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
mkdir -p node-security/node-dist
cd node-security/node-dist

curl -fSLO --retry 3 \
  https://nodejs.org/dist/v26.12.1/node-v26.12.1-linux-x64.tar.xz
curl -fSLO --retry 3 \
  https://nodejs.org/dist/v26.12.1/SHASUMS256.txt

grep ' node-v26.12.1-linux-x64.tar.xz$' SHASUMS256.txt \
  | sha256sum -c -

tar -xJf node-v26.12.1-linux-x64.tar.xz --strip-components=1 \
  node-v26.12.1-linux-x64/bin/node

./bin/node --version

预期看到校验 OK 和 v26.12.1。这里使用 HTTPS 下载并核对官方 SHA-256 清单,没有额外进行发布签名验证。

只替换运行时

在 /opt/misskey/node-security/Dockerfile 写入:

1
2
FROM local/misskey:base-node26.4.0
COPY node-dist/bin/node /usr/local/bin/node

然后构建并单独检查新镜像:

1
2
3
4
5
6
7
8
cd /opt/misskey

docker build --pull=false \
  -t local/misskey:2026.10.0-node26.12.1 ./node-security

docker run --rm --network none --entrypoint node \
  local/misskey:2026.10.0-node26.12.1 \
  -p 'JSON.stringify({node:process.version,modules:process.versions.modules})'

输出为:

新旧运行时的模块 ABI 均为 147,依赖兼容性仍需启动服务验证。

本次仅替换 Node.js 可执行文件,未重新编译 Misskey,也未升级 npm、pnpm 或开发头文件。后续编译原生模块或升级 Misskey 时,需重新检查构建链和基础镜像。

修改 Compose,替换 web 容器

原来的 web 镜像配置是:

1
2
3
services:
  web:
    image: misskey/misskey:2026.10.0

将 web 中这一项替换为下面三项,其余端口、挂载、网络和环境变量保留:

1
2
3
4
5
services:
  web:
    image: local/misskey:2026.10.0-node26.12.1
    pull_policy: never
    build: ./node-security

pull_policy: never 让部署使用本地修补镜像;build 保留重建入口。随后检查配置并替换 web 服务:

1
2
3
4
5
cd /opt/misskey

docker compose config --quiet
docker compose up -d --no-deps --pull never \
  --wait --wait-timeout 180 web

这次只重新创建了 web 容器,PostgreSQL 和 Redis 没有重启。web 切换期间会有短暂中断。

原镜像的启动命令包含数据库迁移和应用启动。虽然此次没有改变 Misskey 应用版本,仍要观察启动是否完成,不能看到容器进入 running 就认定升级成功。

验证正在运行的服务

容器和镜像是否一致

1
2
3
4
5
docker inspect misskey-web-1 \
  --format 'IMAGE={{.Config.Image}} IMAGE_ID={{.Image}} STATUS={{.State.Status}} HEALTH={{.State.Health.Status}}'

docker image inspect local/misskey:2026.10.0-node26.12.1 \
  --format '{{.Id}}'

容器镜像 ID 与本地修补镜像一致,状态为 running,健康检查为 healthy。

运行时和应用版本是否符合预期

1
2
3
4
5
docker compose exec -T web node -p \
  'JSON.stringify({node:process.version,openssl:process.versions.openssl,modules:process.versions.modules})'

docker compose exec -T web node -p \
  'require("/misskey/package.json").version'

实测输出:

1
2
{"node":"v26.12.1","openssl":"3.5.9","modules":"147"}
2026.10.0

页面、API 和日志是否正常

下面域名是占位示例,执行前换成自己的实例地址:

1
2
3
4
5
6
7
8
curl --fail --silent --output /dev/null \
  --write-out 'HTTP %{http_code}\n' https://social.example.com/

curl --fail --silent --request POST \
  --header 'Content-Type: application/json' --data '{}' \
  https://social.example.com/api/ping

docker compose logs --since 5m --tail 100 web

这次首页返回 HTTP 200,/api/ping 返回包含 pong 的 JSON。启动日志显示 worker 已启动并监听端口,数据库和 Redis 也保持健康。

本次验证了应用启动及基本 HTTP/API,未进行漏洞利用复现,也未完整测试登录、上传和联邦交互。

回退与后续维护

若新镜像启动失败,且应用版本未变、未新增数据库迁移,可恢复备份的 compose.yaml,将 web.image 改为已保存的 local/misskey:before-node-security-时间戳,并设置 pull_policy: never。执行以下命令重新创建容器,再核对镜像 ID:

1
2
3
4
cd /opt/misskey
docker compose config --quiet
docker compose up -d --no-deps --no-build --pull never \
  --wait --wait-timeout 180 web

官方标签可能已被前面的 docker pull 更新,回退必须使用保存的本地标签。回退会恢复旧运行时,仅用于临时恢复服务。

如果以后升级了 Misskey 应用,且已经执行数据库迁移,就不能默认“换回旧镜像”一定能回滚,需要结合该版本迁移说明和数据库备份处理。不要执行 docker compose down -v 来升级,它会删除 Compose 管理的数据卷。

当前部署使用的是本地修补镜像。以后官方发布新镜像时,先拉取并独立检查它的 Node.js 版本,再决定是否切回官方镜像;切回时要同步调整 image、pull_policy 和 build,避免继续使用旧修补镜像或误将它重建回来。