# 基础镜像(使用 Alpine,手动处理 OpenSSL 兼容性) ARG BASE_IMAGE=node:18-alpine ARG ALPINE_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/alpine FROM ${BASE_IMAGE} AS base # 安装依赖阶段 FROM base AS deps ARG ALPINE_MIRROR RUN sed -i "s|https://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g" /etc/apk/repositories && \ apk add --no-cache libc6-compat python3 make g++ cairo-dev jpeg-dev pango-dev giflib-dev WORKDIR /app # 复制 package.json(利用 Docker 层缓存) COPY package.json package-lock.json* ./ # canvas 在项目里是可选依赖,且服务端代码已做降级处理。 # 构建机当前卡在 canvas 的 node-pre-gyp 安装阶段,因此这里只在镜像构建时跳过它。 RUN node -e "const fs=require('fs'); const pkg=require('./package.json'); if (pkg.optionalDependencies && pkg.optionalDependencies.canvas) { delete pkg.optionalDependencies.canvas; fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\\n'); }" # 安装依赖(使用淘宝镜像 + BuildKit缓存) RUN --mount=type=cache,target=/root/.npm \ echo "Installing dependencies (using cache if available)..." && \ npm config set registry https://registry.npmmirror.com && \ npm install --prefer-offline --no-audit --no-fund --legacy-peer-deps && \ echo "Dependencies installed ($(du -sh /app/node_modules | cut -f1))" # 构建阶段 FROM base AS builder ARG ALPINE_MIRROR # 安装构建依赖 RUN sed -i "s|https://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g" /etc/apk/repositories && \ apk add --no-cache libc6-compat python3 make g++ cairo-dev jpeg-dev pango-dev giflib-dev ffmpeg openssl WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # 这两个动态路由负责兜底访问本地上传文件;如果它们在构建上下文里丢失, # 线上音频/视频就会直接 404。这里提前失败,避免再把缺路由的镜像发出去。 RUN test -f "/app/app/api/uploads/[...path]/route.ts" && \ test -f "/app/app/uploads/[...path]/route.ts" # 设置环境变量 ENV NEXT_TELEMETRY_DISABLED=1 # 构建时使用假的环境变量(只用于构建,运行时会被容器的真实环境变量覆盖) # 这样 Prisma 和其他模块在构建时不会报错 ENV DATABASE_URL="postgresql://build:build@localhost:5432/build" ENV NEXTAUTH_SECRET="build-time-secret" ENV NEXTAUTH_URL="http://localhost:3000" ENV ADMIN_API_KEY="build-time-key" ENV DINGTALK_CLIENT_ID="build-time-dingtalk-id" ENV DINGTALK_CLIENT_SECRET="build-time-dingtalk-secret" # 构建应用(复用 Next.js 构建缓存) RUN --mount=type=cache,target=/app/.next/cache \ NODE_ENV=production npm run build # 清理 standalone 目录中不需要的文件(减少 1GB+ 体积) RUN echo "清理 standalone 目录..." && \ rm -rf .next/standalone/uploads && \ rm -rf .next/standalone/.build-cache && \ rm -rf .next/standalone/.docker-cache && \ rm -rf .next/standalone/.git && \ rm -rf .next/standalone/__tests__ && \ rm -rf .next/standalone/docs && \ rm -rf .next/standalone/backups && \ rm -rf .next/standalone/.kiro && \ rm -rf .next/standalone/scripts && \ echo "清理完成" # 生产运行阶段 FROM base AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ARG ALPINE_MIRROR RUN sed -i "s|https://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g" /etc/apk/repositories && \ apk add --no-cache ffmpeg python3 cairo jpeg pango giflib openssl poppler-utils RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs # 复制必要文件 COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/app ./app # 只复制运行时必需的 node_modules COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma COPY --from=builder --chown=nextjs:nodejs /app/node_modules/ag-psd ./node_modules/ag-psd COPY --from=builder --chown=nextjs:nodejs /app/node_modules/ioredis ./node_modules/ioredis COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@ioredis ./node_modules/@ioredis # 只复制 schema.prisma,不复制 migrations 和 engine-cache(减少 35MB) COPY --from=builder --chown=nextjs:nodejs /app/prisma/schema.prisma ./prisma/schema.prisma COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib # 创建必要目录并设置权限 RUN mkdir -p /app/public /app/uploads && \ mkdir -p /app/uploads/temp && \ mkdir -p /app/uploads/assembled && \ mkdir -p /app/uploads/materials && \ mkdir -p /app/uploads/cleanup && \ chown -R nextjs:nodejs /app/public /app/uploads && \ chmod -R 777 /app/uploads USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" CMD ["node", "server.js"]