# syntax=docker/dockerfile:1 # docker/server.Dockerfile —— photo-live-server 单镜像多角色(P5-prep Task 1)。 # 决策(docs/plans/2026-09-21-P5-准备件.md Task 1):单镜像 photo-live-server, # CMD 区分 api-cn / api-ov / worker / sync / migrate-deploy / seed-admin, # 减少 1Panel 维护面(一个 tar 导入,多角色复用)。 # # 构建:docker build -f docker/server.Dockerfile -t photo-live-server: . # = git short sha + 日期(见 infra/deploy/README-1Panel.md 版本约定)。 # # 阶段: # build 全量 install(锁文件冻结)→ prisma generate 双库 → 逐包 tsc 构建 # (显式依赖序代替 turbo:镜像内无需 turbo 二进制,链路更短更确定) # prod-deps 仅生产依赖的 workspace install(--prod,供 runtime 复用清单与结构) # runtime node:20-alpine + openssl(prisma musl 引擎依赖)/curl(healthcheck) # + 全局 prisma@5.22.0(migrate-deploy 角色;版本对齐 pnpm-lock.yaml) # + prisma generate 双库(db-cn 生成到 pnpm store 内 .prisma,db-ov # 生成到包内 generated/——运行时本阶段重建,避免拷贝易碎的 store 深路径) # # sharp 在 alpine(musl) 的预编译二进制随 optionalDependencies 分发(0.33+ 无需 # 编译);根 package.json pnpm.onlyBuiltDependencies 已放行其 install 脚本。若 # 未来 sharp 版本在 musl 失败,回退方案:两阶段 base 换 node:20-slim(debian, # glibc 预编译),无需其它改动。 # ── 阶段 1:构建 ── FROM node:20-alpine AS build WORKDIR /app ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 RUN corepack enable # 先拷全部 workspace 清单 → install 层缓存(源码变动不失效)。 COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY apps/admin/package.json apps/admin/ COPY apps/viewer/package.json apps/viewer/ COPY packages/contracts/package.json packages/contracts/ COPY packages/db-cn/package.json packages/db-cn/ COPY packages/db-ov/package.json packages/db-ov/ COPY packages/embed-loader/package.json packages/embed-loader/ COPY packages/storage/package.json packages/storage/ COPY services/api/package.json services/api/ COPY services/sync/package.json services/sync/ COPY services/worker/package.json services/worker/ RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ pnpm install --frozen-lockfile # 源码(.dockerignore 已剔除 node_modules/dist/.env/apps/mobile/docs)。 COPY packages packages COPY services services # prisma generate 双库:db-ov 的 src/index.ts 转出口 generated/client(gitignore # 工件,必须先于 tsc);generate 不需要真实 DATABASE_URL_*,migrate deploy 才需要。 RUN pnpm --filter @photo/db-cn --filter @photo/db-ov exec prisma generate # 逐包构建(依赖序显式;服务端不需要构建 apps/viewer、apps/admin——那是 web 镜像的事)。 RUN pnpm --filter @photo/contracts run build \ && pnpm --filter @photo/db-cn run build \ && pnpm --filter @photo/db-ov run build \ && pnpm --filter @photo/storage run build \ && pnpm --filter @photo/api-cn --filter @photo/worker --filter @photo/sync run build # ── 阶段 2:生产依赖 ── FROM node:20-alpine AS prod-deps WORKDIR /app ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 RUN corepack enable COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY apps/admin/package.json apps/admin/ COPY apps/viewer/package.json apps/viewer/ COPY packages/contracts/package.json packages/contracts/ COPY packages/db-cn/package.json packages/db-cn/ COPY packages/db-ov/package.json packages/db-ov/ COPY packages/embed-loader/package.json packages/embed-loader/ COPY packages/storage/package.json packages/storage/ COPY services/api/package.json services/api/ COPY services/sync/package.json services/sync/ COPY services/worker/package.json services/worker/ # --prod:剔除 jest/ts-node/playwright 等开发依赖;@prisma/client 走 optional # 引擎依赖仍在(.prisma 生成产物由 runtime 阶段的 prisma generate 重建)。 RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ pnpm install --frozen-lockfile --prod # ── 阶段 3:运行时 ── FROM node:20-alpine # openssl:prisma 查询引擎 musl 构建链接 openssl3;curl:compose healthcheck; # tini 未装——compose 侧统一 init: true 承担 PID 1 信号转发。 RUN apk add --no-cache openssl curl # 全局 prisma CLI(migrate-deploy 角色)。版本必须与 pnpm-lock.yaml 的 # prisma@5.22.0 一致,避免 schema/engine 行为漂移。npm 显式重试参数:构建机到 # registry 偶发 ECONNRESET,npm 默认不重试会硬失败(pnpm 自带重试无此问题)。 RUN --mount=type=cache,target=/root/.npm \ npm install -g --fetch-retries=5 --fetch-retry-mintimeout=20000 \ --fetch-retry-maxtimeout=120000 prisma@5.22.0 ENV NODE_ENV=production WORKDIR /app # workspace 结构(清单 + 各包 node_modules 符号链接,pnpm 相对链接布局完整保留)。 COPY --from=prod-deps /app/node_modules ./node_modules COPY --from=prod-deps /app/packages ./packages COPY --from=prod-deps /app/services ./services # 构建产物。 COPY --from=build /app/packages/contracts/dist ./packages/contracts/dist COPY --from=build /app/packages/db-cn/dist ./packages/db-cn/dist COPY --from=build /app/packages/db-ov/dist ./packages/db-ov/dist COPY --from=build /app/packages/storage/dist ./packages/storage/dist COPY --from=build /app/services/api/dist ./services/api/dist COPY --from=build /app/services/worker/dist ./services/worker/dist COPY --from=build /app/services/sync/dist ./services/sync/dist # prisma schema + 迁移(migrate-deploy 角色与 generate 都在包内 prisma/ 目录)。 COPY packages/db-cn/prisma ./packages/db-cn/prisma COPY packages/db-ov/prisma ./packages/db-ov/prisma # 双库 client 生成:db-cn → pnpm store 内 .prisma/client;db-ov → 包内 generated/ # (运行时镜像内重建,产物即最终布局;docker build 网络可下载 musl 引擎)。 RUN prisma generate --schema=packages/db-cn/prisma/schema.prisma \ && prisma generate --schema=packages/db-ov/prisma/schema.prisma COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] # 缺省角色 api-cn;compose 用 command: 覆盖(api-ov/worker/sync/migrate-deploy)。 CMD ["api-cn"]