# ─────────────────────────────────────────────────────────────────────────── # Readest Lite — 多阶段构建 Dockerfile # 单镜像:Next.js standalone + Prisma + SQLite + 本地文件存储 # ─────────────────────────────────────────────────────────────────────────── # ── Stage 1: dependencies ────────────────────────────────────────────────── FROM docker.io/library/node:24-slim AS dependencies ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable && corepack prepare pnpm@11.1.1 --activate # 安装 git 用于 clone 子模块(foliate-js 与 simplecc-wasm) RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # 拷贝 monorepo 配置 + 应用 package.json COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY apps/readest-app/package.json ./apps/readest-app/ COPY patches/ ./patches/ # Clone 必需的 git submodule(原项目用 .gitmodules,但 Docker 构建上下文里这些目录为空, # 这里直接 git clone --depth 1 取 main 分支)。 # web 构建需要: # - foliate-js(workspace 包 + pdfjs vendors) # - simplecc-wasm(dist/web/* 用于 setup-vendors) # - js-mdict(tsconfig.json path mapping 引用 ../../packages/js-mdict/src/index.ts) # tauri/tauri-plugins/qcms 在 web 构建中不需要(tauri-plugin-turso 在 nativeDatabaseService.ts # 已 stub 掉,且 tsconfig path mapping 会被 next.config.mjs 的 webpack alias 跳过)。 RUN git clone --depth 1 https://github.com/readest/foliate-js.git packages/foliate-js \ && git clone --depth 1 https://github.com/readest/simplecc-wasm.git packages/simplecc-wasm \ && git clone --depth 1 https://github.com/readest/js-mdict.git packages/js-mdict # 安装依赖(包含 Prisma CLI 与 argon2/jwt 等新增依赖) # 注 1:原 lockfile 未包含新增的 @prisma/client/argon2/jsonwebtoken 等,所以用 # --no-frozen-lockfile 让 pnpm 解析并更新 lockfile。如需可重现构建,可在本地 # 跑一次 pnpm install 后提交更新后的 pnpm-lock.yaml,再改回 --frozen-lockfile。 # 注 2:pnpm 11 默认跳过依赖的 build scripts(安全考虑),但 @prisma/client / # argon2 / prisma 都需要在 install 时跑 native build。用 # --config.dangerouslyAllowAllBuilds=true 显式放行(与 pnpm-workspace.yaml # 的 onlyBuiltDependencies 二选一即可,这里双保险)。 RUN --mount=type=cache,id=pnpm,sharing=locked,target=/pnpm/store \ pnpm install --no-frozen-lockfile --config.dangerouslyAllowAllBuilds=true # 验证 submodule 已正确 clone RUN test -f packages/foliate-js/vendor/pdfjs/annotation_layer_builder.css \ && test -d packages/simplecc-wasm/dist/web \ || { printf '\nERROR: Required submodules are not initialized.\n'; exit 1; } # 拷贝 vendor 资源到 public/vendor RUN pnpm --filter @readest/readest-app setup-vendors # 在 dependencies 阶段就生成 Prisma Client(此时网络可用,prisma CLI 可正常自检)。 # 后续 build 阶段直接复用生成好的 client,不再调 prisma generate。 # # Prisma 5/6 在 generate 时会尝试 'pnpm add prisma@ -D --silent' 自动安装 # 自己(即使已装),这在 Docker 中因 lockfile 冲突失败。 # Workaround:临时把 pnpm 替换为 stub(什么都不做、返回 0),跑完 generate 再恢复。 # # DATABASE_URL 必须设置,否则 prisma generate 会报 getConfig Validation Error COPY prisma ./prisma ENV DATABASE_URL="file:/tmp/readest.db" RUN printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/pnpm-stub && chmod +x /usr/local/bin/pnpm-stub RUN cp $(which pnpm) /usr/local/bin/pnpm.real && mv /usr/local/bin/pnpm-stub $(which pnpm) RUN cd apps/readest-app && \ node node_modules/prisma/build/index.js generate --schema=../../prisma/schema.prisma RUN mv /usr/local/bin/pnpm.real $(which pnpm) # ── Stage 2: build ───────────────────────────────────────────────────────── FROM docker.io/library/node:24-slim AS build ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable && corepack prepare pnpm@11.1.1 --activate WORKDIR /app # 构建参数 — Readest Lite 改造:所有 URL 类变量不再烤死绝对路径 # 前端代码运行时用 window.location.origin / 相对路径 '/api', # 用户从任何域名访问都能正常工作。 # NEXT_PUBLIC_SUPABASE_URL 设为空字符串(前端 getSupabaseUrl() 会用 window.location.origin) # NEXT_PUBLIC_API_BASE_URL 设为相对路径 /api ARG NEXT_PUBLIC_SUPABASE_URL= ARG NEXT_PUBLIC_SUPABASE_ANON_KEY=anon ARG NEXT_PUBLIC_APP_PLATFORM=web ARG NEXT_PUBLIC_API_BASE_URL=/api ARG NEXT_PUBLIC_OBJECT_STORAGE_TYPE=local ARG NEXT_PUBLIC_STORAGE_FIXED_QUOTA=0 ARG NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA=0 ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY ENV NEXT_PUBLIC_APP_PLATFORM=$NEXT_PUBLIC_APP_PLATFORM ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL ENV NEXT_PUBLIC_OBJECT_STORAGE_TYPE=$NEXT_PUBLIC_OBJECT_STORAGE_TYPE ENV NEXT_PUBLIC_STORAGE_FIXED_QUOTA=$NEXT_PUBLIC_STORAGE_FIXED_QUOTA ENV NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA=$NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA # 从 dependencies 阶段拷贝已安装好的 node_modules 和 packages 子模块 COPY --from=dependencies /app/node_modules /app/node_modules COPY --from=dependencies /app/apps/readest-app/node_modules /app/apps/readest-app/node_modules COPY --from=dependencies /app/apps/readest-app/public/vendor /app/apps/readest-app/public/vendor COPY --from=dependencies /app/packages/foliate-js /app/packages/foliate-js COPY --from=dependencies /app/packages/foliate-js/node_modules /app/packages/foliate-js/node_modules COPY --from=dependencies /app/packages/simplecc-wasm /app/packages/simplecc-wasm COPY --from=dependencies /app/packages/js-mdict /app/packages/js-mdict # 拷贝项目源码 COPY . . WORKDIR /app/apps/readest-app # Prisma Client 已在 dependencies 阶段生成(位于 apps/readest-app/node_modules/.prisma/client) # 此阶段无需再跑 prisma generate(会因网络受限失败) # Opt-in standalone build ENV BUILD_STANDALONE=true ENV NODE_OPTIONS="--max-old-space-size=4096" RUN pnpm build-web # ── Stage 3: production runtime ──────────────────────────────────────────── FROM docker.io/library/node:24-slim AS production ENV NODE_ENV=production ENV PORT=8225 ENV HOSTNAME=0.0.0.0 ENV DATABASE_URL="file:/data/db/readest.db" ENV BOOKS_DIR="/data/books" ENV INBOX_DIR="/data/inbox" WORKDIR /app # 安装最小运行时依赖:openssl(argon2 需要)、sqlite3(prisma 需要)、curl(健康检查) RUN apt-get update && apt-get install -y --no-install-recommends \ openssl sqlite3 ca-certificates curl \ && rm -rf /var/lib/apt/lists/* # 拷贝 standalone 构建产物 COPY --from=build --chown=node:node /app/apps/readest-app/.next/standalone ./ COPY --from=build --chown=node:node /app/apps/readest-app/.next/static ./apps/readest-app/.next/static COPY --from=build --chown=node:node /app/apps/readest-app/public ./apps/readest-app/public # 拷贝 Prisma schema + init 脚本 COPY --from=build --chown=node:node /app/prisma ./prisma COPY --from=build --chown=node:node /app/apps/readest-app/scripts ./apps/readest-app/scripts # 拷贝运行时需要的 node_modules。 # standalone 构建会 trace Next.js 的依赖,但 prisma CLI 用于容器启动时 # 的 db push,需要完整的 node_modules 树(pnpm 的符号链接结构在 COPY 后 # 会断裂,所以需要拷贝整个 apps/readest-app/node_modules)。 # 这会让镜像增大约 300-500MB,但保证了 prisma CLI、argon2、jsonwebtoken # 都能正常工作。 COPY --from=build --chown=node:node /app/apps/readest-app/node_modules ./apps/readest-app/node_modules COPY --from=build --chown=node:node /app/node_modules/.pnpm ./node_modules/.pnpm # 创建数据目录 RUN mkdir -p /data/db /data/books /data/inbox && chown -R node:node /data USER node EXPOSE 8225 # 启动脚本:先 prisma db push 同步 schema + 初始化管理员,再启 Next.js COPY --chown=node:node docker/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]