diff --git a/Dockerfile b/Dockerfile index d895e00..32a1de9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,36 @@ -# ===== 构建阶段 ===== -FROM node:22-bullseye AS builder +# ===== 构建阶段(Alpine 减小体积)===== +# 未传入的 build-arg 使用占位符,便于运行阶段用环境变量替换 +# Supabase 构建时会校验 URL,故使用合法占位 URL,运行时再替换 +FROM node:22-alpine AS builder WORKDIR /app -ARG NEXT_PUBLIC_SUPABASE_URL -ARG NEXT_PUBLIC_SUPABASE_ANON_KEY -ARG NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY -ARG NEXT_PUBLIC_GA_ID -ARG NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL + +ARG NEXT_PUBLIC_SUPABASE_URL=https://runtime-replace.supabase.co +ARG NEXT_PUBLIC_SUPABASE_ANON_KEY=__NEXT_PUBLIC_SUPABASE_ANON_KEY__ +ARG NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY=__NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY__ +ARG NEXT_PUBLIC_GA_ID=__NEXT_PUBLIC_GA_ID__ +ARG NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL=__NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL__ ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY ENV NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY=$NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY ENV NEXT_PUBLIC_GA_ID=$NEXT_PUBLIC_GA_ID ENV NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL=$NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL + COPY package*.json ./ -RUN npm install --legacy-peer-deps +RUN npm ci --legacy-peer-deps + COPY . . RUN npx next build -# ===== 运行阶段 ===== -FROM node:22-bullseye AS runner -WORKDIR /app -ENV NODE_ENV=production -ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL -ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY -ENV NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY=$NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY -ENV NEXT_PUBLIC_GA_ID=$NEXT_PUBLIC_GA_ID -ENV NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL=$NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL -COPY --from=builder /app/package.json ./ -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/.next ./.next + +# ===== 运行阶段(仅静态资源 + nginx,启动时替换占位符)===== +FROM nginx:alpine AS runner +WORKDIR /usr/share/nginx/html + +COPY --from=builder /app/out . +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ - CMD wget -qO- http://localhost:3000 || exit 1 -CMD ["npm", "start"] + CMD curl -f http://localhost:3000/ || exit 1 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index 46887be..c0c2465 100644 --- a/README.md +++ b/README.md @@ -111,18 +111,27 @@ npm run build ### Docker运行 -需先配置环境变量(与本地开发一致),否则构建出的镜像中 Supabase 等配置为空。可复制 `env.example` 为 `.env` 并填入实际值;若不用登录/反馈功能可留空。 +镜像支持两种配置方式: -1. 构建镜像(构建时会读取当前环境或同目录 `.env` 中的变量) +- **构建时写入**:构建时通过 `--build-arg` 或 `.env` 传入 `NEXT_PUBLIC_*`,值会打进镜像,运行时无需再传。 +- **运行时替换**:构建时不传(或使用默认占位符),启动容器时通过 `-e` 或 `--env-file` 传入,入口脚本会在启动 Nginx 前替换静态资源中的占位符。 + +可复制 `env.example` 为 `.env` 并填入实际值;若不用登录/反馈功能可留空。 + +1. 构建镜像 ```bash +# 方式 A:运行时再注入配置(镜像内为占位符) docker build -t real-time-fund . -# 或通过 --build-arg 传入,例如: -# docker build -t real-time-fund --build-arg NEXT_PUBLIC_Supabase_URL=xxx --build-arg NEXT_PUBLIC_Supabase_ANON_KEY=xxx --build-arg NEXT_PUBLIC_GA_ID=G-xxxx . + +# 方式 B:构建时写入配置 +docker build -t real-time-fund --build-arg NEXT_PUBLIC_SUPABASE_URL=xxx --build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY=xxx . +# 或依赖同目录 .env:docker compose build ``` 2. 启动容器 ```bash -docker run -d -p 3000:3000 --name fund real-time-fund +# 若构建时未写入配置,可在此注入(与 --env-file .env 二选一) +docker run -d -p 3000:3000 --name fund --env-file .env real-time-fund ``` #### docker-compose(会读取同目录 `.env` 作为 build-arg 与运行环境) @@ -131,6 +140,29 @@ docker run -d -p 3000:3000 --name fund real-time-fund docker compose up -d ``` +### Docker Hub + +镜像已发布至 Docker Hub,可直接拉取运行,无需本地构建。 + +1. **拉取镜像** + ```bash + docker pull hzm0321/real-time-fund:latest + ``` + +2. **启动容器** + 访问 [http://localhost:3000](http://localhost:3000) 即可使用。 + ```bash + docker run -d -p 3000:3000 --name real-time-fund --restart always hzm0321/real-time-fund:latest + ``` + +3. **使用自定义环境变量(运行时替换)** + 镜像内已预置占位符,启动时通过环境变量即可覆盖,无需重新构建。例如使用本地 `.env`: + ```bash + docker run -d -p 3000:3000 --name real-time-fund --restart always --env-file .env hzm0321/real-time-fund:latest + ``` + 或单独指定变量:`-e NEXT_PUBLIC_SUPABASE_URL=xxx -e NEXT_PUBLIC_SUPABASE_ANON_KEY=xxx`。 + 变量名与本地开发一致:`NEXT_PUBLIC_SUPABASE_URL`、`NEXT_PUBLIC_SUPABASE_ANON_KEY`、`NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY`、`NEXT_PUBLIC_GA_ID`、`NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL`。 + ## 📖 使用说明 1. **添加基金**:在顶部输入框输入 6 位基金代码(如 `110022`),点击“添加”。 diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..c5d0a0e --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# 在启动 Nginx 前,将静态资源中的占位符替换为运行时环境变量 +set -e + +HTML_ROOT="/usr/share/nginx/html" + +# 转义 sed 替换串中的特殊字符:\ & | +escape_sed() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/&/\\&/g; s/|/\\|/g' +} + +# 占位符与环境变量对应(占位符名 = 变量名) +replace_var() { + placeholder="$1" + value=$(escape_sed "${2:-}") + find "$HTML_ROOT" -type f \( -name '*.js' -o -name '*.html' \) -exec sed -i "s|${placeholder}|${value}|g" {} \; +} + +# URL 构建时使用合法占位,此处替换为运行时环境变量 +replace_var "https://runtime-replace.supabase.co" "${NEXT_PUBLIC_SUPABASE_URL}" +replace_var "__NEXT_PUBLIC_SUPABASE_ANON_KEY__" "${NEXT_PUBLIC_SUPABASE_ANON_KEY}" +replace_var "__NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY__" "${NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY}" +replace_var "__NEXT_PUBLIC_GA_ID__" "${NEXT_PUBLIC_GA_ID}" +replace_var "__NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL__" "${NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL}" + +exec nginx -g "daemon off;" diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..6e453f2 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,15 @@ +server { + listen 3000; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri.html $uri/ /index.html =404; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +}