# Docker 部署(Package 镜像) 补充架构说明见: - `deploy/docker/ARCHITECTURE.md` - `deploy/caddy/Caddyfile.tohka.example` - `deploy/caddy/Caddyfile.tohka.production.example` - `deploy/docker/TOHKA_POCKET_ID.md` - `deploy/docker/TOHKA_DEPLOY_RUNBOOK.md` - `deploy/docker/config.yaml.example` - `deploy/docker/BACKUP_AND_RECOVERY.md` ## 1) 准备主配置文件(config.yaml) 现在推荐把: - `deploy/docker/config.yaml` 作为部署主配置源;`deploy/docker/.env` 改为脚本生成产物。 先复制模板: ```bash cp deploy/docker/config.yaml.example deploy/docker/config.yaml ``` 然后填写至少这些核心项: - `compose_env.DATABASE_URL` - `compose_env.REDIS_URL` - `compose_env.JWT_SECRET` - 邮件确认 / 邮件通知上线前,请同时补齐 SMTP 配置 填完后执行: ```bash python deploy/scripts/render_compose_env.py \ --input deploy/docker/config.yaml \ --output deploy/docker/.env ``` 如果你们内部喜欢先审 YAML,再部署,这就是现在的推荐流程。 建议在 `config.yaml -> compose_env` 下同时检查这些运行时变量: - `INTERNAL_API_BASE_URL`:frontend SSR 容器访问 backend 用,compose 默认推荐 `http://backend:5150/api` - `PUBLIC_API_BASE_URL`:浏览器访问 backend API 用;留空时前台会回退到“当前主机 + `:5150/api`” - `PUBLIC_COMMENT_TURNSTILE_SITE_KEY`:前台评论 / 订阅表单使用的 Cloudflare Turnstile site key - `PUBLIC_WEB_PUSH_VAPID_PUBLIC_KEY`:前台浏览器推送订阅使用的 VAPID public key - `PUBLIC_IMAGE_ALLOWED_HOSTS`:前台 `/_img` 图片优化端点允许的额外图片 host(逗号分隔) - `ADMIN_API_BASE_URL`:admin 浏览器访问 backend API 用;留空时后台会回退到“当前主机 + `:5150`” - `ADMIN_FRONTEND_BASE_URL`:admin 里“打开前台 / 问答页 / 文章页预览”跳转用 - `TERMI_ADMIN_TRUST_PROXY_AUTH`:是否信任前置代理(如 Caddy + TinyAuth)注入的后台认证头 - `TERMI_ADMIN_LOCAL_LOGIN_ENABLED`:是否保留本地账号密码登录兜底 - `TERMI_ADMIN_PROXY_SHARED_SECRET`:代理 SSO 共享密钥;建议和 Caddy 的 `X-Termi-Proxy-Secret` 配套使用 - `TERMI_TURNSTILE_SECRET_KEY`:backend 评论 / 订阅接口使用的 Cloudflare Turnstile secret key(兼容旧的 `TERMI_COMMENT_TURNSTILE_SECRET_KEY`) - `TERMI_WEB_PUSH_VAPID_PRIVATE_KEY`:backend / worker 发送浏览器推送时使用的 VAPID private key - `TERMI_WEB_PUSH_VAPID_SUBJECT`:浏览器推送 VAPID subject,推荐 `mailto:xxx@example.com` - `SMTP_ENABLE / SMTP_HOST / SMTP_PORT / SMTP_SECURE / SMTP_USER / SMTP_PASSWORD / SMTP_HELLO_NAME`:订阅确认和邮件通知需要 例如: ```yaml compose_env: PUBLIC_API_BASE_URL: https://api.blog.init.cool PUBLIC_COMMENT_TURNSTILE_SITE_KEY: 1x00000000000000000000AA PUBLIC_WEB_PUSH_VAPID_PUBLIC_KEY: replace-with-web-push-vapid-public-key ADMIN_API_BASE_URL: https://admin.blog.init.cool ADMIN_FRONTEND_BASE_URL: https://blog.init.cool TERMI_ADMIN_TRUST_PROXY_AUTH: true TERMI_ADMIN_LOCAL_LOGIN_ENABLED: false TERMI_ADMIN_PROXY_SHARED_SECRET: replace-with-a-long-random-secret TERMI_TURNSTILE_SECRET_KEY: replace-with-turnstile-secret-key TERMI_WEB_PUSH_VAPID_PRIVATE_KEY: replace-with-web-push-vapid-private-key TERMI_WEB_PUSH_VAPID_SUBJECT: mailto:noreply@blog.init.cool ``` > 这些值最终会被渲染成 `deploy/docker/.env`,再由 `compose.package.yml` 读取。 > 如果镜像构建期也注入了 `FRONTEND_PUBLIC_API_BASE_URL` / `ADMIN_VITE_API_BASE` / `ADMIN_VITE_FRONTEND_BASE_URL`,则运行时变量优先级更高。 ## 2) 启动 在仓库根目录执行: ```bash python deploy/scripts/render_compose_env.py \ --input deploy/docker/config.yaml \ --output deploy/docker/.env docker compose -f deploy/docker/compose.package.yml --env-file deploy/docker/.env up -d ``` 如果是在 `tohka` 上并且前面接宿主机 Caddy,推荐改用: ```bash python deploy/scripts/render_compose_env.py \ --input deploy/docker/config.yaml \ --output deploy/docker/.env docker compose \ -f deploy/docker/compose.package.yml \ -f deploy/docker/compose.tohka.override.yml \ --env-file deploy/docker/.env up -d ``` 当前 compose 推荐一起启动 4 个容器: - `backend`:API / migration / `/healthz` - `backend-worker`:Redis 队列消费者(通知异步投递、失败重试) - `frontend`:Astro SSR(Node) - `admin`:静态 SPA + Nginx `compose.tohka.override.yml` 额外会把三个对外端口绑到 `127.0.0.1`,避免把 backend / admin 直接暴露到公网。 ## 3) 更新镜像后重拉 ```bash docker compose -f deploy/docker/compose.package.yml --env-file deploy/docker/.env pull docker compose -f deploy/docker/compose.package.yml --env-file deploy/docker/.env up -d ``` ## 4) 问答记录(2026-03-31) ### Q1: 为什么前台容器是 `4321` 端口? A: 前台是 **Astro SSR(Node)**(`output: 'server'`),容器内运行 Node 服务(`dist/server/entry.mjs`),所以需要监听内部端口 `4321`。 ### Q2: 两个前端都能做成纯静态 + 反代吗? A: - `admin` 可以,当前就是静态资源由 Nginx 提供。 - `frontend` 当前不行(未做纯静态改造):项目里使用了 `prerender = false`、`Astro.request`、`Astro.cookies` 等请求期逻辑。 ### Q3: SSR 和 CSR 怎么选? A: 当前站点对外内容页优先 SEO 与首屏可见性,保留 SSR 更稳;后台管理台继续 CSR/静态站即可。 ### Q4: 生产推荐端口设计是什么? A: 推荐前置 Caddy/Nginx 统一暴露 `80/443`,`frontend:4321` / `backend:5150` / `admin:80` 仅走内网。 当前 `compose.package.yml` 属于直连端口版,便于快速部署与联调。 另外因为通知已经走异步队列,生产务必同时启动 `backend-worker`。 ### Q5: 为什么 compose 里没看到 `ADMIN_VITE_FRONTEND_BASE_URL`? A: - 现在 compose 运行时可以直接设置 `ADMIN_FRONTEND_BASE_URL`,用于覆盖 admin 里所有前台跳转链接。 - `ADMIN_VITE_FRONTEND_BASE_URL` 仍然可以作为 **镜像构建期默认值** 保留在 CI / workflow 里。 - 如果两者都存在,**运行时 `ADMIN_FRONTEND_BASE_URL` 优先**。 ### Q6: 前台 / 后台为什么拆成 public/internal 两种 API 地址? A: - `frontend` 是 Astro SSR,服务端渲染请求 backend 时更适合走内网地址(如 `http://backend:5150/api`)。 - 但浏览器里的评论、问答、搜索请求必须走用户可访问的公开地址。 - `admin` 是纯静态 SPA,也只能使用浏览器可访问的公开 API 地址。 - 所以现在区分为: - `INTERNAL_API_BASE_URL`:frontend SSR 内部访问 - `PUBLIC_API_BASE_URL`:前台浏览器访问 - `ADMIN_API_BASE_URL`:admin 浏览器访问 ### Q7: 现在后台 OIDC / Pocket ID 怎么接? A: - 推荐直接复用 tohka 上现成的 **TinyAuth + Pocket ID**。 - Caddy 在 `admin` 域名入口加 `import tinyauth`,并把 `/api/*` 同域转发到 backend。 - backend 开启: - `TERMI_ADMIN_TRUST_PROXY_AUTH=true` - `TERMI_ADMIN_LOCAL_LOGIN_ENABLED=false` - `TERMI_ADMIN_PROXY_SHARED_SECRET=<随机长字符串>` - Caddy 在 `/api/*` -> backend 时补: - `X-Termi-Proxy-Secret: {$TERMI_ADMIN_PROXY_SHARED_SECRET}` - backend 会信任 TinyAuth 转发的: - `Remote-User` - `Remote-Email` - `Remote-Groups` - 本地开发仍可保留内置账号密码登录。 ### Q8: 备份现在放在哪看? A: - 参考脚本:`deploy/scripts/backup/` - 恢复文档:`deploy/docker/BACKUP_AND_RECOVERY.md` ### Q9: 现在健康检查和 migration 怎么处理? A: - `backend` 镜像启动时会先执行 `db migrate` - `backend` 提供 `/healthz` - `frontend` 提供 `/healthz` - `admin` 继续由 Nginx 提供 `/healthz` - compose 现在使用 `depends_on.condition: service_healthy`