From f0a6da915a70f4dca75d6d230970cf4f83dba853 Mon Sep 17 00:00:00 2001 From: SepComet <202308010230@stu.csust.edu.cn> Date: Wed, 6 May 2026 10:42:50 +0800 Subject: [PATCH] Add Docker Compose deployment setup --- .dockerignore | 10 ++ Dockerfile | 52 ++++++++++ README.md | 19 ++++ TODO.md | 8 +- docker-compose.yml | 26 +++++ docker/nginx/default.conf | 18 ++++ docs/docker-deploy.md | 195 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 docker/nginx/default.conf create mode 100644 docs/docker-deploy.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cb00179 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +node_modules +dist +.git +.gitignore +.env +.env.* +.astro +.omc +.omx +.codex diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d633c08 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +FROM node:22-alpine AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . + +ARG GITEA_BASE_URL="" +ARG GITEA_TOKEN="" +ARG GITEA_USERNAME="" +ARG SEAFILE_BASE_URL="" +ARG SEAFILE_TOKEN="" +ARG SYNC_OUTPUT_DIR="src/data/generated" +ARG STRICT_SYNC="false" +ARG SEAFILE_MIRROR_DOWNLOADS="false" +ARG DOWNLOADS_OUTPUT_DIR="public/downloads" +ARG GITEA_ACTIVITY_DAYS="70" +ARG GITEA_ACTIVITY_PER_DAY_LIMIT="50" +ARG GITEA_RECENT_ITEM_LIMIT="8" +ARG GITEA_REQUEST_TIMEOUT_MS="15000" +ARG GITEA_REQUEST_CONCURRENCY="5" +ARG SEAFILE_REQUEST_TIMEOUT_MS="15000" + +ENV GITEA_BASE_URL="${GITEA_BASE_URL}" \ + GITEA_TOKEN="${GITEA_TOKEN}" \ + GITEA_USERNAME="${GITEA_USERNAME}" \ + SEAFILE_BASE_URL="${SEAFILE_BASE_URL}" \ + SEAFILE_TOKEN="${SEAFILE_TOKEN}" \ + SYNC_OUTPUT_DIR="${SYNC_OUTPUT_DIR}" \ + STRICT_SYNC="${STRICT_SYNC}" \ + SEAFILE_MIRROR_DOWNLOADS="${SEAFILE_MIRROR_DOWNLOADS}" \ + DOWNLOADS_OUTPUT_DIR="${DOWNLOADS_OUTPUT_DIR}" \ + GITEA_ACTIVITY_DAYS="${GITEA_ACTIVITY_DAYS}" \ + GITEA_ACTIVITY_PER_DAY_LIMIT="${GITEA_ACTIVITY_PER_DAY_LIMIT}" \ + GITEA_RECENT_ITEM_LIMIT="${GITEA_RECENT_ITEM_LIMIT}" \ + GITEA_REQUEST_TIMEOUT_MS="${GITEA_REQUEST_TIMEOUT_MS}" \ + GITEA_REQUEST_CONCURRENCY="${GITEA_REQUEST_CONCURRENCY}" \ + SEAFILE_REQUEST_TIMEOUT_MS="${SEAFILE_REQUEST_TIMEOUT_MS}" + +RUN npm run rebuild + +FROM nginx:1.27-alpine AS runtime + +COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/dist /usr/share/nginx/html + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget -q -O /dev/null http://127.0.0.1/ || exit 1 diff --git a/README.md b/README.md index 725ac1e..46acaf9 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ npm run rebuild - `TODO.md`:当前推进计划 - `docs/rebuild-trigger-spec.md`:AstrBot / cron 重建触发与错误码约定 - `docs/content-sync-guide.md`:环境变量、映射文件、同步脚本职责、故障排查 +- `docs/docker-deploy.md`:Docker / Compose 部署说明 - `docs/ui-design-spec.md`:UI 设计规范 ## 内容入口 @@ -121,6 +122,24 @@ REBUILD_RESULT {...} - `docs/rebuild-trigger-spec.md` +## Docker / Compose 部署 + +最小启动方式: + +```bash +docker compose up -d --build +``` + +默认端口: + +```text +http://localhost:8080 +``` + +详细说明见: + +- `docs/docker-deploy.md` + ## 当前实现边界 - 当前默认策略是失败时回退 seed data,而不是保留上一轮 generated 文件 diff --git a/TODO.md b/TODO.md index 28ff97a..d8f761b 100644 --- a/TODO.md +++ b/TODO.md @@ -89,10 +89,10 @@ ### 4. 补部署文件与部署说明 -- [ ] 增加 `Dockerfile` -- [ ] 增加 `docker-compose.yml` -- [ ] 明确静态产物部署方式 -- [ ] 写清部署步骤与重建入口 +- [x] 增加 `Dockerfile` +- [x] 增加 `docker-compose.yml` +- [x] 明确静态产物部署方式 +- [x] 写清部署步骤与重建入口 ### 5. 补运行文档 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..733b681 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +services: + homepage: + build: + context: . + dockerfile: Dockerfile + args: + GITEA_BASE_URL: ${GITEA_BASE_URL:-} + GITEA_TOKEN: ${GITEA_TOKEN:-} + GITEA_USERNAME: ${GITEA_USERNAME:-} + SEAFILE_BASE_URL: ${SEAFILE_BASE_URL:-} + SEAFILE_TOKEN: ${SEAFILE_TOKEN:-} + SYNC_OUTPUT_DIR: ${SYNC_OUTPUT_DIR:-src/data/generated} + STRICT_SYNC: ${STRICT_SYNC:-false} + SEAFILE_MIRROR_DOWNLOADS: ${SEAFILE_MIRROR_DOWNLOADS:-false} + DOWNLOADS_OUTPUT_DIR: ${DOWNLOADS_OUTPUT_DIR:-public/downloads} + GITEA_ACTIVITY_DAYS: ${GITEA_ACTIVITY_DAYS:-70} + GITEA_ACTIVITY_PER_DAY_LIMIT: ${GITEA_ACTIVITY_PER_DAY_LIMIT:-50} + GITEA_RECENT_ITEM_LIMIT: ${GITEA_RECENT_ITEM_LIMIT:-8} + GITEA_REQUEST_TIMEOUT_MS: ${GITEA_REQUEST_TIMEOUT_MS:-15000} + GITEA_REQUEST_CONCURRENCY: ${GITEA_REQUEST_CONCURRENCY:-5} + SEAFILE_REQUEST_TIMEOUT_MS: ${SEAFILE_REQUEST_TIMEOUT_MS:-15000} + image: personal-homepage:latest + container_name: personal-homepage + ports: + - "${HOMEPAGE_PORT:-8080}:80" + restart: unless-stopped diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf new file mode 100644 index 0000000..0074251 --- /dev/null +++ b/docker/nginx/default.conf @@ -0,0 +1,18 @@ +server { + listen 80; + listen [::]:80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ $uri/index.html =404; + } + + location = /50x.html { + internal; + } + + error_page 500 502 503 504 /50x.html; +} diff --git a/docs/docker-deploy.md b/docs/docker-deploy.md new file mode 100644 index 0000000..dd46fb5 --- /dev/null +++ b/docs/docker-deploy.md @@ -0,0 +1,195 @@ +# Docker / Compose 部署说明 + +当前提供的是最小可用静态部署方案: + +- 构建阶段:在 Docker build 中执行 `npm run rebuild` +- 运行阶段:使用 Nginx 提供 `dist/` 静态文件 + +## 文件 + +- `Dockerfile` +- `docker-compose.yml` +- `docker/nginx/default.conf` + +## 使用方式 + +### 1. 准备环境变量 + +确保仓库根目录有可用的 `.env`,或在执行 `docker compose` 前导出这些变量: + +- `GITEA_BASE_URL` +- `GITEA_TOKEN` +- `GITEA_USERNAME` +- `SEAFILE_BASE_URL` +- `SEAFILE_TOKEN` + +可选: + +- `STRICT_SYNC` +- `HOMEPAGE_PORT` +- 其余同步调优参数 + +`docker-compose.yml` 会把这些值作为 **build args** 传给 Dockerfile,用于构建时同步和静态生成。 + +### 环境变量是怎么注入的 + +当前链路是: + +```text +宿主机环境变量 / 项目根目录 .env + -> docker compose 读取变量 + -> docker-compose.yml 的 build.args + -> Dockerfile 的 ARG + -> Dockerfile 的 ENV + -> npm run rebuild +``` + +也就是说,`docker-compose.yml` 自己并不保存真实值,它只是引用变量,例如: + +```yaml +args: + GITEA_BASE_URL: ${GITEA_BASE_URL:-} +``` + +含义是: + +- 如果宿主机环境或 `.env` 里有 `GITEA_BASE_URL`,就把它传进去 +- 如果没有,就用空字符串 + +然后 `Dockerfile` 再接住: + +```dockerfile +ARG GITEA_BASE_URL="" +ENV GITEA_BASE_URL="${GITEA_BASE_URL}" +``` + +这样 `npm run rebuild` 在镜像构建阶段就能读取到这些变量。 + +### `.env` 和 compose 的关系 + +当前项目里,同一个 `.env` 同时服务两条路径: + +1. **本地直接执行** + +```bash +npm run rebuild +``` + +Node 通过: + +```bash +--env-file-if-exists=.env +``` + +读取 `.env` + +2. **Docker Compose 构建** + +```bash +docker compose up -d --build +``` + +Compose 会自动读取项目根目录 `.env`,并将变量传给 `build.args` + +所以可以把 `.env` 理解成: + +- 本地脚本运行配置 +- Docker 构建时同步配置 + +共用的一份构建环境变量文件。 + +### 为什么用 build args 而不是运行时环境变量 + +因为这个项目的 Gitea / Seafile token 只在**构建阶段**需要: + +- 构建时同步远端数据 +- 构建时生成静态页面 + +最终运行时容器只是 Nginx 提供 `dist/`,不再访问 Gitea / Seafile,也不需要 token。 + +### 修改环境变量后要怎么生效 + +因为变量是注入到 **构建阶段** 的,所以修改 `.env` 后需要重新 build: + +```bash +docker compose up -d --build +``` + +如果只执行: + +```bash +docker compose restart +``` + +不会重新同步,也不会重新生成静态产物。 + +### 2. 构建并启动 + +```bash +docker compose up -d --build +``` + +默认会把站点暴露到: + +```text +http://localhost:8080 +``` + +如需修改端口: + +```bash +HOMEPAGE_PORT=80 docker compose up -d --build +``` + +### 3. 查看日志 + +```bash +docker compose logs -f homepage +``` + +### 4. 停止 + +```bash +docker compose down +``` + +## 部署方式说明 + +当前 compose 方案是: + +1. 构建镜像时执行 `npm run rebuild` +2. 生成 `dist/` +3. 将 `dist/` 拷贝进 Nginx 容器 +4. 运行时只提供静态文件服务 + +这意味着: + +- 运行中的容器不需要 Gitea / Seafile token +- token 只在镜像构建阶段使用 +- 每次内容更新后,需要重新 build 镜像 + +## 与 AstrBot 的配合方式 + +如果后续由 AstrBot 触发重建,推荐: + +1. AstrBot 在服务器上拉最新代码 +2. AstrBot 执行 `docker compose up -d --build` +3. AstrBot 根据命令结果发送成功 / 失败通知 + +如果你想让 AstrBot 先单独判断同步 / 构建错误码,再决定是否部署,也可以先执行: + +```bash +npm run rebuild +``` + +通过后再执行: + +```bash +docker compose up -d --build +``` + +## 当前限制 + +- compose 里还没有反向代理、HTTPS、域名配置 +- 还没有单独拆“构建容器”和“运行容器”的外部流水线 +- 还没有持久化部署产物或零停机切换策略