Umami 3.0.3 构建失败问题修复

上个月本地部署了 umami,但是今天在修复一个 bug 时,自动构建报错了。

[00:01:36   +0ms] #14 [runner  6/12] RUN pnpm --allow-build='@prisma/engines' add npm-run-all dotenv chalk semver     prisma@6.19.0     @prisma/adapter-pg@6.19.0
[00:01:36 +652ms] #14 77.12 Progress: resolved 162, reused 0, downloaded 147, added 0
[00:01:37  +1.0s] #14 78.12 Progress: resolved 185, reused 0, downloaded 182, added 0
[00:01:38  +1.0s] #14 79.12 Progress: resolved 185, reused 0, downloaded 183, added 0
[00:01:39 +999ms] #14 80.12 Progress: resolved 187, reused 0, downloaded 184, added 0
[00:01:40 +950ms] #14 81.07 Packages: +188
[00:01:40   +0ms] #14 81.07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[00:01:40 +202ms] #14 81.12 Progress: resolved 188, reused 0, downloaded 186, added 29
[00:01:41 +849ms] #14 82.12 Progress: resolved 188, reused 0, downloaded 188, added 187
[00:01:42  +1.0s] #14 83.15 Progress: resolved 188, reused 0, downloaded 188, added 188
[00:01:43 +188ms] #14 83.15 Progress: resolved 188, reused 0, downloaded 188, added 188, done
[00:01:43   +0ms] #14 83.19 .../node_modules/@prisma/engines postinstall$ node scripts/postinstall.js
[00:01:45  +2.9s] #14 86.25 .../node_modules/@prisma/engines postinstall: Done
[00:01:46 +285ms] #14 86.53 
[00:01:46   +0ms] #14 86.53 dependencies:
[00:01:46   +0ms] #14 86.53 + @prisma/adapter-pg 6.19.0 (7.8.0 is available)
[00:01:46   +0ms] #14 86.53 + chalk 5.6.2
[00:01:46   +0ms] #14 86.53 + dotenv 17.4.2
[00:01:46   +0ms] #14 86.53 + npm-run-all 4.1.5
[00:01:46   +0ms] #14 86.53 + prisma 6.19.0 (7.8.0 is available)
[00:01:46   +0ms] #14 86.53 + semver 7.8.0
[00:01:46   +0ms] #14 86.53 
[00:01:46 +212ms] #14 86.60 [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: prisma@6.19.0
[00:01:46   +0ms] #14 86.60 
[00:01:46   +0ms] #14 86.60 Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
[00:01:46 +289ms] #14 ERROR: process "/bin/sh -c pnpm --allow-build='@prisma/engines' add npm-run-all dotenv chalk semver     prisma@${PRISMA_VERSION}     @prisma/adapter-pg@${PRISMA_VERSION}" did not complete successfully: exit code: 1
[00:01:53  +6.6s] ------
[00:01:53   +0ms]  > [runner  6/12] RUN pnpm --allow-build='@prisma/engines' add npm-run-all dotenv chalk semver     prisma@6.19.0     @prisma/adapter-pg@6.19.0:
[00:01:53   +0ms] 86.53 + @prisma/adapter-pg 6.19.0 (7.8.0 is available)
[00:01:53   +0ms] 86.53 + chalk 5.6.2
[00:01:53   +0ms] 86.53 + dotenv 17.4.2
[00:01:53   +0ms] 86.53 + npm-run-all 4.1.5
[00:01:53   +0ms] 86.53 + prisma 6.19.0 (7.8.0 is available)
[00:01:53   +0ms] 86.53 + semver 7.8.0
[00:01:53   +0ms] 86.53 
[00:01:53   +0ms] 86.60 [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: prisma@6.19.0
[00:01:53   +0ms] 86.60 
[00:01:53   +0ms] 86.60 Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
[00:01:53   +0ms] ------
[00:01:53   +0ms] WARNING: current commit information was not captured by the build: git was not found in the system: exec: "git": executable file not found in $PATH
[00:01:53   +1ms] Dockerfile:45
[00:01:53   +0ms] --------------------
[00:01:53   +0ms]   44 |     # Script dependencies
[00:01:53   +0ms]   45 | >>> RUN pnpm --allow-build='@prisma/engines' add npm-run-all dotenv chalk semver \
[00:01:53   +0ms]   46 | >>>     prisma@${PRISMA_VERSION} \
[00:01:53   +0ms]   47 | >>>     @prisma/adapter-pg@${PRISMA_VERSION}
[00:01:53   +0ms]   48 |     
[00:01:53   +0ms] --------------------
[00:01:53   +0ms] ERROR: failed to build: failed to solve: process "/bin/sh -c pnpm --allow-build='@prisma/engines' add npm-run-all dotenv chalk semver     prisma@${PRISMA_VERSION}     @prisma/adapter-pg@${PRISMA_VERSION}" did not complete successfully: exit code: 1
[00:01:53  +12ms] exit code: 1, signal: -1, duration: 1.8m
  

CNB 自动的 AI 分析提供了几个方案,但是改了几次都没成功。最后还是在项目中直接把错误日志发给了 Codex,让它直接修改 Dockerfile。

Codex 的方案

原因是 runner 阶段执行 pnpm add 时没有复制 pnpm-workspace.yaml,pnpm 10 读不到项目里的 onlyBuiltDependencies 白名单;同时命令只允许了 @prisma/engines,导致 prisma@6.19.0 的 build script 被拦截,触发:

[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: prisma@6.19.0

我改了 Dockerfile:8:

  • deps 阶段复制 pnpm-workspace.yaml,让安装依赖时也读取 build-script 白名单
  • 全局 pnpm 固定为 pnpm@10
  • runner 阶段复制 pnpm-workspace.yaml
  • runner 阶段创建最小 package.json,然后用 pnpm add -w ... 安装脚本依赖,避免 workspace root 安装歧义
  • 移除了只允许 @prisma/engines--allow-build,改为使用项目已有白名单,其中已经包含 prisma

修改前的 Dockerfile

ARG NODE_IMAGE_VERSION="22-alpine"

# Install dependencies only when needed
FROM node:${NODE_IMAGE_VERSION} AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile

# Rebuild the source code only when needed
FROM node:${NODE_IMAGE_VERSION} AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
COPY docker/middleware.ts ./src

ARG BASE_PATH

ENV BASE_PATH=$BASE_PATH
ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_URL="postgresql://user:pass@localhost:5432/dummy"

RUN npm run build-docker

# Production image, copy all the files and run next
FROM node:${NODE_IMAGE_VERSION} AS runner
WORKDIR /app

ARG PRISMA_VERSION="6.19.0"
ARG NODE_OPTIONS

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_OPTIONS=$NODE_OPTIONS

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN set -x \
    && apk add --no-cache curl \
    && npm install -g pnpm

# Script dependencies
RUN pnpm --allow-build='@prisma/engines' add npm-run-all dotenv chalk semver \
    prisma@${PRISMA_VERSION} \
    @prisma/adapter-pg@${PRISMA_VERSION}

COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/scripts ./scripts
COPY --from=builder /app/generated ./generated

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV HOSTNAME=0.0.0.0
ENV PORT=3000

CMD ["pnpm", "start-docker"]
  

修改后的 Dockerfile

ARG NODE_IMAGE_VERSION="22-alpine"

# Install dependencies only when needed
FROM node:${NODE_IMAGE_VERSION} AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN npm install -g pnpm@10
RUN pnpm install --frozen-lockfile

# Rebuild the source code only when needed
FROM node:${NODE_IMAGE_VERSION} AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
COPY docker/middleware.ts ./src

ARG BASE_PATH

ENV BASE_PATH=$BASE_PATH
ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_URL="postgresql://user:pass@localhost:5432/dummy"

RUN npm run build-docker

# Production image, copy all the files and run next
FROM node:${NODE_IMAGE_VERSION} AS runner
WORKDIR /app

ARG PRISMA_VERSION="6.19.0"
ARG NODE_OPTIONS

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_OPTIONS=$NODE_OPTIONS

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN set -x \
    && apk add --no-cache curl \
    && npm install -g pnpm@10

# Script dependencies
COPY pnpm-workspace.yaml ./
RUN printf '{"name":"umami-runner","private":true,"version":"0.0.0"}\n' > package.json \
    && pnpm add -w npm-run-all dotenv chalk semver \
    prisma@${PRISMA_VERSION} \
    @prisma/adapter-pg@${PRISMA_VERSION}

COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/scripts ./scripts
COPY --from=builder /app/generated ./generated

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV HOSTNAME=0.0.0.0
ENV PORT=3000

CMD ["pnpm", "start-docker"]
  

改动的稍微有些多,但是这个版本已经可以正常构建了。

保守方案

由于提供的是编译的错误日志,AI 自然就根据错误日志的内容来提供解决方案。

pnpm-workspace.yaml 文件中确实是有 ignoredBuiltDependenciesonlyBuiltDependencies 等配置,但是这个 Dockerfile 在一个月前还是可以正常构建的。

最近一段时间发生了什么会导致这个问题呢?

由于是运行到 pnpm 命令的脚本报错的,自然就是去 npmjs 上看一下 pnpm 是否有什么改动,果然上个月发布了 11 版本。

于是尝试仅仅把两处 npm install -g pnpm 改成 npm install -g pnpm@10,然后构建就成功了。

由于本人前端并不太了解,为了避免构建脚本改动过大造成未知的影响,最后还是采用第二种比较保守的方案。

pnpm 10 和 11 的对比

问了下豆包 10 和 11 的区别:

维度pnpm 10pnpm 11
Node.js 支持18+22+(仅 ESM)
存储索引多 JSON 文件单一 SQLite(index.db)
安全默认宽松(无最小发布龄)严格(1 天延迟 + 阻止外部依赖)
构建脚本控制多配置分散统一 allowBuilds
全局安装共享依赖,易冲突完全隔离,独立目录
发布依赖调用 npm CLI原生实现,无 npm 依赖
性能快(硬链接)更快(SQLite+ 减少 I/O)

其中 构建脚本控制 的改动大概率就是造成本次构建失败的原因。pnpm 11 统一为 allowBuilds,默认仅允许受信任包执行构建脚本,以减少恶意代码执行风险。