From f55113069c78b6d9552850918cc5afaca76b70d8 Mon Sep 17 00:00:00 2001 From: kennethcheng Date: Sun, 3 May 2026 04:35:36 +0800 Subject: [PATCH] =?UTF-8?q?build(docker):=20=E5=A2=9E=E5=8A=A0=E5=A4=9A?= =?UTF-8?q?=E9=98=B6=E6=AE=B5=20Dockerfile=20=E4=B8=8E=E7=BC=96=E6=8E=92?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=EF=BC=8C=E5=AE=9E=E7=8E=B0=E7=94=9F=E4=BA=A7?= =?UTF-8?q?=E7=BA=A7=E6=97=A0=E7=8A=B6=E6=80=81=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 9 +++++++++ Dockerfile | 37 +++++++++++++++++++++++++++++++++++++ Memory.md | 10 ++++++++++ docker-compose.yml | 15 +++++++++++++++ next.config.ts | 2 +- 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1e6001a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +.next +.git +.env +.env.* +Memory.md +Dockerfile +docker-compose.yml +README.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ca0fa9d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# 阶段 1:安装依赖 +FROM node:18-alpine AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci + +# 阶段 2:构建产物 +FROM node:18-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# 禁用 Next.js 遥测,并注入占位环境变量以通过编译 +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +# 阶段 3:生产运行环境 +FROM node:18-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# 安全降权运行 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs +EXPOSE 8080 +# 确保 Next.js 监听所有网卡并在正确端口启动 +ENV PORT=8080 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/Memory.md b/Memory.md index 8f1a148..4c5f79f 100644 --- a/Memory.md +++ b/Memory.md @@ -305,6 +305,16 @@ - 在 `src/actions/snapshots.ts` 中引入 `desc` 与 `gte` 操作符,彻底替换原始 SQL 模板拼接(`sql`"${date}" DESC``),消除 `ReferenceError: date is not defined` 运行时错误。 - 使用 `desc(portfolioSnapshots.date)` 实现降序排列,使用 `gte(portfolioSnapshots.date, startDate)` 实现日期范围过滤,并添加 `.$dynamic()` 支持动态条件拼接。 +## 执行 Task 90:完成项目无状态 Docker 容器化改造。配置 standalone 模式、多阶段 Dockerfile 及 docker-compose 编排,实现外部 PgSQL 密钥的运行时动态注入隔离 (Task 90) +- **Next.js Standalone 模式**:在 `next.config.ts` 中增加 `output: 'standalone'` 属性,构建时自动生成 `/.next/standalone` 目录,仅包含运行所需的最小文件集,大幅缩减镜像体积。 +- **.dockerignore 防腐层**:创建 `.dockerignore` 排除 `node_modules`、`.next`、`.git`、`.env` 等敏感和无用文件,防止污染镜像上下文。 +- **三阶段多阶段构建 Dockerfile**: + - **阶段 1 (deps)**:基于 `node:18-alpine` 安装依赖,使用 `npm ci` 实现锁死版本的确定性安装。 + - **阶段 2 (builder)**:复用 deps 阶段的 `node_modules`,完整复制项目源码并执行 `npm run build`,禁用 Next.js 遥测 (`NEXT_TELEMETRY_DISABLED=1`)。 + - **阶段 3 (runner)**:极简生产环境,仅复制 `.next/standalone`、`.next/static` 和 `public` 目录;创建非 root 用户 `nextjs` (uid: 1001) 实现安全降权;暴露 8080 端口并监听 `0.0.0.0`。 +- **Docker Compose 编排**:`docker-compose.yml` 配置 `env_file: .env` 实现运行时环境变量动态注入(数据库 URL、CRON_SECRET 等敏感密钥不打包进镜像);配置 `healthcheck` 使用 `wget` 进行健康探测,每 30 秒检查一次。 +- **架构红线**:所有生产敏感配置(数据库连接串、CRON_SECRET 等)必须通过 `.env` 文件在运行时注入,严禁硬编码或打包进 Docker 镜像层。 + ## 持倉引擎 Native 幣種算法重構 (Task 38) - 重構底層盈虧引擎,全面轉向 Native 原生幣種計算,新增浮動/累計盈虧及百分比指標。 - 徹底分離 Native 與 CNY 計算:單隻股票的成本與盈虧全部改用 Native (原幣種) 進行計算。 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9a031ad --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3.8' +services: + web: + build: . + container_name: stock-portfolio-web + ports: + - "8080:8080" + env_file: + - .env + restart: always + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/"] + interval: 30s + timeout: 5s + retries: 3 diff --git a/next.config.ts b/next.config.ts index 21695b5..4cac033 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: 'standalone', allowedDevOrigins: [ '10.10.10.1', // 允许该IP访问 // 'your-custom-domain.dev', // 如果有自定义域名也可以加在这里