|
1 | | -FROM node:lts-alpine |
| 1 | +# ---------------------------------------------------- |
| 2 | +# 阶段 1: 依赖安装和 Prisma 客户端生成 (deps) |
| 3 | +# ---------------------------------------------------- |
| 4 | +FROM oven/bun:1-alpine AS deps |
2 | 5 |
|
3 | | -ENV NODE_ENV=production |
| 6 | +WORKDIR /app |
| 7 | + |
| 8 | +# 复制依赖文件(利用 Docker 缓存层) |
| 9 | +COPY package.json bun.lock* ./ |
| 10 | + |
| 11 | +# 🚨 关键:只安装生产环境依赖! |
| 12 | +RUN bun install --production --frozen-lockfile |
| 13 | + |
| 14 | +# 复制 schema 并生成 Prisma 客户端 |
| 15 | +COPY prisma ./prisma |
| 16 | +RUN bunx prisma generate |
| 17 | + |
| 18 | +# ---------------------------------------------------- |
| 19 | +# 阶段 2: 代码编译 (builder) |
| 20 | +# ---------------------------------------------------- |
| 21 | +FROM oven/bun:1-alpine AS builder |
4 | 22 |
|
5 | | -# 安装运行时必需的包和工具(合并 RUN 减少层数) |
6 | | -RUN apk add --no-cache openssl su-exec && \ |
7 | | - addgroup -g 1001 -S nodejs && \ |
8 | | - adduser -S nodejs -u 1001 |
| 23 | +WORKDIR /app |
9 | 24 |
|
10 | | -WORKDIR /usr/src/app |
| 25 | +# 安装所有依赖(包括 devDependencies,编译时需要 TypeScript) |
| 26 | +COPY package.json bun.lock* ./ |
| 27 | +RUN bun install --frozen-lockfile |
11 | 28 |
|
12 | | -# 复制依赖文件和源代码 |
13 | | -COPY package.json package-lock.json* ./ |
| 29 | +# 从 deps 阶段复制 Prisma Client(避免重新生成) |
| 30 | +COPY --from=deps /app/node_modules/.prisma ./node_modules/.prisma |
| 31 | + |
| 32 | +# 复制源代码 |
14 | 33 | COPY . . |
15 | 34 |
|
16 | | -# 创建启动脚本和入口脚本(合并 RUN 减少层数) |
17 | | -RUN cat > /usr/src/app/docker-start.sh << 'EOFSCRIPT' && chmod +x /usr/src/app/docker-start.sh && \ |
18 | | - cat > /usr/src/app/docker-entrypoint.sh << 'EOFENTRY' && chmod +x /usr/src/app/docker-entrypoint.sh |
19 | | -#!/bin/sh |
20 | | -set -e |
21 | | - |
22 | | -# 检查 node_modules 是否存在且 package.json 未变更 |
23 | | -if [ ! -d "node_modules" ] || [ ! -f "node_modules/.package-lock.json" ] || [ package.json -nt node_modules/.package-lock.json ]; then |
24 | | - echo "📦 Installing production dependencies..." |
25 | | - if [ -f "package-lock.json" ]; then |
26 | | - npm ci --omit=dev || npm install --omit=dev |
27 | | - else |
28 | | - npm install --omit=dev |
29 | | - fi |
30 | | - echo "📦 Installing build tools..." |
31 | | - npm install --save-dev ts-node prisma typescript @types/node || true |
32 | | - npm cache clean --force |
33 | | - touch node_modules/.package-lock.json |
34 | | -else |
35 | | - echo "✅ Using cached dependencies" |
36 | | - if [ ! -d "node_modules/ts-node" ]; then |
37 | | - echo "📦 Installing build tools..." |
38 | | - npm install --save-dev ts-node prisma typescript @types/node || true |
39 | | - fi |
40 | | -fi |
41 | | - |
42 | | -echo "🔧 Generating Prisma Client..." |
43 | | -npx prisma generate |
44 | | - |
45 | | -echo "🔧 Pushing database schema..." |
46 | | -npx prisma db push || true |
47 | | - |
48 | | -echo "🚀 Starting server..." |
49 | | -exec npm start |
50 | | -EOFSCRIPT |
51 | | -#!/bin/sh |
52 | | -set -e |
53 | | - |
54 | | -# 如果 node_modules 目录存在(volume 挂载),修复权限 |
55 | | -if [ -d "node_modules" ]; then |
56 | | - echo "🔧 Fixing node_modules permissions..." |
57 | | - chown -R nodejs:nodejs node_modules 2>/dev/null || true |
58 | | -fi |
59 | | - |
60 | | -# 切换到 nodejs 用户并执行启动脚本 |
61 | | -exec su-exec nodejs /usr/src/app/docker-start.sh |
62 | | -EOFENTRY |
63 | | - |
64 | | -# 设置文件权限(以 root 身份) |
65 | | -RUN chown -R nodejs:nodejs /usr/src/app |
66 | | - |
67 | | -# 保持 root 用户,在 entrypoint 中切换 |
| 35 | +# 编译所有 TypeScript 代码到 dist 目录 |
| 36 | +RUN bun run build |
| 37 | + |
| 38 | +# ---------------------------------------------------- |
| 39 | +# 阶段 3: 运行时镜像 (runner) - 极致瘦身! |
| 40 | +# ---------------------------------------------------- |
| 41 | +FROM oven/bun:1-alpine AS runner |
| 42 | + |
| 43 | +WORKDIR /app |
| 44 | + |
| 45 | +# 创建非 root 用户(安全最佳实践) |
| 46 | +RUN addgroup --system --gid 1001 nodejs && \ |
| 47 | + adduser --system --uid 1001 bunuser |
| 48 | + |
| 49 | +# 复制 package.json 和 lockfile(用于重新安装生产依赖) |
| 50 | +COPY --from=builder --chown=bunuser:nodejs /app/package.json ./ |
| 51 | +COPY --from=builder --chown=bunuser:nodejs /app/bun.lock* ./ |
| 52 | + |
| 53 | +# 🚨 关键优化:在 runner 阶段重新安装生产依赖 |
| 54 | +# 这样可以确保完全没有 devDependencies,并且只安装运行时需要的包 |
| 55 | +RUN bun install --production --frozen-lockfile |
| 56 | + |
| 57 | +# 从 deps 阶段复制 Prisma Client 和 schema(Prisma 是生产依赖) |
| 58 | +# 注意:先复制 node_modules,再复制 .prisma,确保目录结构正确 |
| 59 | +COPY --from=deps --chown=bunuser:nodejs /app/node_modules/.prisma ./node_modules/.prisma |
| 60 | +COPY --from=deps --chown=bunuser:nodejs /app/prisma ./prisma |
| 61 | + |
| 62 | +# 确保 Prisma Client 目录权限正确 |
| 63 | +RUN chown -R bunuser:nodejs /app/node_modules/.prisma || true |
| 64 | + |
| 65 | +# 从 builder 阶段复制编译后的代码 |
| 66 | +COPY --from=builder --chown=bunuser:nodejs /app/dist ./dist |
| 67 | + |
| 68 | +# 🔥 终极清理:删除所有不必要的文件 |
| 69 | +RUN rm -rf /app/node_modules/.cache && \ |
| 70 | + rm -rf /tmp/* && \ |
| 71 | + rm -rf /var/cache/apk/* && \ |
| 72 | + # 🚨 强制删除 TypeScript 及其相关包(Prisma 的可选 peer dependency,生产环境不需要) |
| 73 | + rm -rf /app/node_modules/typescript && \ |
| 74 | + rm -rf /app/node_modules/@typescript-eslint && \ |
| 75 | + rm -rf /app/node_modules/typescript-eslint && \ |
| 76 | + # 🚨 Prisma 优化:删除不需要的数据库引擎文件(只保留 PostgreSQL) |
| 77 | + find /app/node_modules/prisma/build -name '*cockroachdb*' -delete 2>/dev/null || true && \ |
| 78 | + find /app/node_modules/prisma/build -name '*mysql*' -delete 2>/dev/null || true && \ |
| 79 | + find /app/node_modules/prisma/build -name '*sqlite*' -delete 2>/dev/null || true && \ |
| 80 | + find /app/node_modules/prisma/build -name '*sqlserver*' -delete 2>/dev/null || true && \ |
| 81 | + # 删除测试相关目录 |
| 82 | + find /app/node_modules -type d \( -name "test" -o -name "tests" -o -name "__tests__" -o -name "*.test.*" -o -name "*.spec.*" -o -name "docs" -o -name "doc" -o -name "examples" -o -name "example" -o -name "benchmark" -o -name "benchmarks" \) -exec rm -rf {} + 2>/dev/null || true && \ |
| 83 | + # 删除文档和配置文件 |
| 84 | + find /app/node_modules -type f \( -name "*.md" -o -name "*.map" -o -name "CHANGELOG*" -o -name "LICENSE*" -o -name "README*" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" ! -name "package.json" \) ! -path "*/node_modules/.prisma/*" ! -path "*/node_modules/@types/*" -delete 2>/dev/null || true && \ |
| 85 | + # 删除类型定义文件(运行时不需要) |
| 86 | + find /app/node_modules -type f -name "*.d.ts" ! -path "*/node_modules/.prisma/*" ! -path "*/node_modules/@types/*" -delete 2>/dev/null || true && \ |
| 87 | + # 删除源码文件(只保留编译后的代码) |
| 88 | + find /app/node_modules -type f \( -name "*.js" ! -name "*.min.js" \) -path "*/src/*" -delete 2>/dev/null || true |
| 89 | + |
| 90 | +# 设置环境变量,提升性能 |
| 91 | +ENV NODE_ENV=production |
| 92 | + |
| 93 | +# 切换到非 root 用户 |
| 94 | +USER bunuser |
| 95 | + |
| 96 | +# 暴露端口 |
68 | 97 | EXPOSE 3000 |
69 | 98 |
|
70 | | -ENTRYPOINT ["/usr/src/app/docker-entrypoint.sh"] |
| 99 | +# 健康检查 |
| 100 | +HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ |
| 101 | + CMD bun -e "fetch('http://localhost:3000/v2/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))" || exit 1 |
| 102 | + |
| 103 | +# 启动命令:指向编译后的入口文件 |
| 104 | +CMD ["bun", "run", "dist/server.js"] |
0 commit comments