28 KiB
Sport Era Production Docker Compose Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 为 /www/wwwroot/sbnews 创建统一的生产 Docker Compose,使 API、定时任务、Crawler、KB Worker、Admin 构建版和 uni-app H5 构建版通过宝塔域名稳定运行。
Architecture: PHP、Scheduler、Crawler、KB Worker 和三个 Nginx 服务使用 host 网络,直接访问宿主机 MySQL、Redis及回环端口。Admin/H5 使用 Node 20 多阶段构建并由独立 Nginx 托管,宝塔只负责公网域名与 HTTPS 反向代理。
Tech Stack: Docker Compose v2、PHP 8.0 FPM、ThinkPHP、Nginx、Node.js 20、Vite、uni-app、Python 3.11、MySQL、Redis。
文件职责
新增或修改的文件按职责分为以下单元:
- 生产密钥边界:
.gitignore、docker/.env.server.production.example、docker/.env.crawler.production.example、server/config/cache.php。 - H5 构建配置:
uniapp/src/config/index.ts。 - PHP/API 运行时:
docker/config/php/conf.d/zz-opcache.prod.ini、docker/config/php/php-fpm.prod.conf、docker/config/nginx/conf.d/sport-era.prod-api.conf。 - Admin 静态镜像:
docker/Dockerfile.admin、admin/.dockerignore、docker/config/nginx/static/admin.conf。 - H5 静态镜像:
docker/Dockerfile.uniapp-h5、uniapp/.dockerignore、docker/config/nginx/static/uniapp-h5.conf。 - 统一编排:
docker/docker-compose.prod.yml。 - 运维交付:
docs/生产环境Docker部署说明.md、docs/业务进度管理.md。
现有工作区包含其他未提交改动。每次提交前必须使用 git status --short 和 git diff --cached --stat,只暂存本任务列出的文件。
Task 1: 建立生产密钥边界并移除 Redis 密码回退
Files:
-
Modify:
.gitignore:35 -
Create:
docker/.env.server.production.example -
Create:
docker/.env.crawler.production.example -
Modify:
server/config/cache.php:36 -
Step 1: 运行变更前静态检查
Run:
rg -n "\.env\.server\.production|\.env\.crawler\.production" .gitignore
rg -n "'password' => env\('cache\.password', ''\)" server/config/cache.php
Expected: 两条命令都没有匹配;第一条退出码为 1,第二条退出码为 1。
- Step 2: 忽略真实生产环境文件
在 .gitignore 的 Docker 私有配置区域追加:
docker/.env.server.production
docker/.env.crawler.production
- Step 3: 创建 PHP 生产环境模板
创建 docker/.env.server.production.example:
APP_DEBUG = false
DASHSCOPE_API_KEY = change_me
[APP]
DEFAULT_TIMEZONE = Asia/Shanghai
[DATABASE]
TYPE = mysql
HOSTNAME = 127.0.0.1
DATABASE = sbnews
USERNAME = sbnews
PASSWORD = change_me
HOSTPORT = 3300
CHARSET = utf8mb4
DEBUG = false
PREFIX = la_
[CACHE]
DRIVER = redis
HOST = 127.0.0.1
PORT = 6377
PASSWORD = change_me
SELECT = 2
[LANG]
default_lang = zh-cn
[PROJECT]
UNIQUE_IDENTIFICATION = likeadmin
DEMO_ENV = false
- Step 4: 创建 Crawler/KB Worker 生产环境模板
创建 docker/.env.crawler.production.example:
TZ=Asia/Shanghai
DQD_DB_HOST=127.0.0.1
DQD_DB_PORT=3300
DQD_DB_NAME=sbnews
DQD_DB_USER=sbnews
DQD_DB_PASSWORD=change_me
DQD_DB_PREFIX=la_
DQD_DB_CHARSET=utf8mb4
DQD_DB_POOL_SIZE=5
DQD_REDIS_HOST=127.0.0.1
DQD_REDIS_PORT=6377
DQD_REDIS_PASSWORD=change_me
DQD_REDIS_DB=2
DQD_LOG_LEVEL=INFO
DQD_LOG_FILE=/app/logs/crawler.log
EMBEDDING_API_KEY=change_me
EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode
EMBEDDING_MODEL=text-embedding-v4
EMBEDDING_DIM=1024
EMBEDDING_BATCH_SIZE=10
- Step 5: 清空 Redis 密码源码回退
将 server/config/cache.php 中 Redis 密码配置改为:
// 密码
'password' => env('cache.password', ''),
不要改动 Redis 默认主机、端口、前缀和数据库编号。
- Step 6: 验证密钥边界
Run:
rg -n "docker/\.env\.server\.production|docker/\.env\.crawler\.production" .gitignore
rg -n "'password' => env\('cache\.password', ''\)" server/config/cache.php
rg -n "PASSWORD = change_me|DQD_DB_PASSWORD=change_me|DQD_REDIS_PASSWORD=change_me|EMBEDDING_API_KEY=change_me" docker/.env.server.production.example docker/.env.crawler.production.example
git diff --check -- .gitignore server/config/cache.php docker/.env.server.production.example docker/.env.crawler.production.example
Expected: 忽略规则、空密码回退和所有模板占位符均匹配;git diff --check 无输出。
- Step 7: 提交密钥边界变更
git add -- .gitignore server/config/cache.php docker/.env.server.production.example docker/.env.crawler.production.example
git diff --cached --stat
git commit -m "chore: secure production runtime secrets"
Expected: 提交只包含上述四个文件。
- Step 8: 按项目规定同步 Server 配置到测试服
Run:
Set-Location D:\www\gs-sport-era
powershell -ExecutionPolicy Bypass -File .\scripts\deploy-server.ps1 -Sudo -AutoIncremental -AutoIncrementalCommits 3 -RemoteDir /www/wwwroot/test-server.sbnews.net
Expected: 部署脚本成功完成;测试服继续从私有 .env.server 获取 Redis 密码,接口不因默认值清空而中断。
Task 2: 让 uni-app H5 生产构建使用 API 构建变量
Files:
-
Modify:
uniapp/src/config/index.ts:1-18 -
Step 1: 确认当前配置未读取 Vite 环境变量
Run:
rg -n "import\.meta\.env\.VITE_APP_BASE_URL" uniapp/src/config/index.ts
Expected: 没有匹配,退出码为 1。
- Step 2: 改写 API 基础地址初始化
将 uniapp/src/config/index.ts 改为:
const configuredBaseUrl =
import.meta.env.VITE_APP_BASE_URL || 'https://test-server.sbnews.net'
const envBaseUrl = configuredBaseUrl.replace(/\/+$/, '')
let baseUrl = `${envBaseUrl}/`
/*
* 微信小程序在`VITE_APP_BASE_URL`存在或`dev`模式下
* 使用`VITE_APP_BASE_URL`的值
* 其他情况使用`[baseUrl]`,方便服务端替换
*/
//#ifdef MP-WEIXIN
baseUrl = `${envBaseUrl}/`
//#endif
const config = {
version: '1.9.0', //版本号
baseUrl, //请求接口域名
urlPrefix: 'api', //请求默认前缀
timeout: 60 * 1000 //请求超时时长
}
export default config
默认值继续指向测试 API;生产 Docker 构建显式传入正式 API 地址。
- Step 3: 静态验证 URL 规范化
Run:
rg -n "VITE_APP_BASE_URL|replace\(/\\/\+\$/|test-server\.sbnews\.net" uniapp/src/config/index.ts
git diff --check -- uniapp/src/config/index.ts
Expected: 三项配置均匹配;git diff --check 无输出。
- Step 4: 提交 H5 构建配置
git add -- uniapp/src/config/index.ts
git diff --cached --stat
git commit -m "build: make h5 api url environment driven"
Expected: 提交只包含 uniapp/src/config/index.ts。按项目边界不自动执行 H5 构建。
Task 3: 创建生产 PHP-FPM、OPcache 与 API Nginx 配置
Files:
-
Create:
docker/config/php/conf.d/zz-opcache.prod.ini -
Create:
docker/config/php/php-fpm.prod.conf -
Create:
docker/config/nginx/conf.d/sport-era.prod-api.conf -
Step 1: 创建生产 OPcache 配置
创建 docker/config/php/conf.d/zz-opcache.prod.ini:
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=30000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
opcache.save_comments=1
保持时间戳检查,因为生产 Server 使用源码挂载;git pull 后无需依赖镜像层变化才能刷新 PHP 字节码。
- Step 2: 将 PHP-FPM 限制到宿主机回环地址
创建 docker/config/php/php-fpm.prod.conf:
[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1
- Step 3: 创建 API Nginx 配置
创建 docker/config/nginx/conf.d/sport-era.prod-api.conf:
server {
listen 127.0.0.1:8000;
server_name api.sbnews.net;
root /likeadmin_php/server/public;
index index.php index.html;
client_max_body_size 20M;
access_log /logs/sport-era-prod_access.log;
error_log /logs/sport-era-prod_error.log;
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
location / {
try_files $uri $uri/ /index.php?s=$uri&$args;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
fastcgi_read_timeout 300s;
}
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff2?)$ {
expires 7d;
add_header Cache-Control "public";
try_files $uri =404;
}
location ~ /\. {
deny all;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
}
- Step 4: 静态检查回环监听与 FastCGI 目标
Run:
rg -n "validate_timestamps=1|revalidate_freq=2" docker/config/php/conf.d/zz-opcache.prod.ini
rg -n "listen = 127\.0\.0\.1:9000|allowed_clients = 127\.0\.0\.1" docker/config/php/php-fpm.prod.conf
rg -n 'listen 127\.0\.0\.1:8000|fastcgi_pass 127\.0\.0\.1:9000|location = /healthz|try_files \$uri' docker/config/nginx/conf.d/sport-era.prod-api.conf
git diff --check -- docker/config/php/conf.d/zz-opcache.prod.ini docker/config/php/php-fpm.prod.conf docker/config/nginx/conf.d/sport-era.prod-api.conf
Expected: 所有安全监听和路由配置均匹配;git diff --check 无输出。
- Step 5: 提交生产 PHP/API 配置
git add -- docker/config/php/conf.d/zz-opcache.prod.ini docker/config/php/php-fpm.prod.conf docker/config/nginx/conf.d/sport-era.prod-api.conf
git diff --cached --stat
git commit -m "build: add production php and api runtime"
Expected: 提交只包含三个生产运行配置文件。
Task 4: 创建 Admin 与 H5 多阶段静态镜像
Files:
-
Create:
admin/.dockerignore -
Create:
uniapp/.dockerignore -
Create:
docker/Dockerfile.admin -
Create:
docker/Dockerfile.uniapp-h5 -
Create:
docker/config/nginx/static/admin.conf -
Create:
docker/config/nginx/static/uniapp-h5.conf -
Step 1: 创建 Admin 构建上下文忽略文件
创建 admin/.dockerignore:
node_modules
dist
dist-ssr
.git
.env
.env.*
npm-debug.log*
yarn-debug.log*
yarn-error.log*
- Step 2: 创建 H5 构建上下文忽略文件
创建 uniapp/.dockerignore:
node_modules
dist
dist-ssr
unpackage
.hbuilderx
.git
.env
.env.*
npm-debug.log*
yarn-debug.log*
yarn-error.log*
- Step 3: 创建 Admin 多阶段 Dockerfile
创建 docker/Dockerfile.admin:
# syntax=docker/dockerfile:1
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --no-audit --no-fund
COPY . .
ARG VITE_APP_BASE_URL=https://api.sbnews.net
ENV VITE_APP_BASE_URL=${VITE_APP_BASE_URL}
RUN npx vite build
FROM registry.cn-guangzhou.aliyuncs.com/likeadmin/nginx:1.23.1
COPY --from=build /app/dist/ /usr/share/nginx/html/
- Step 4: 创建 uni-app H5 多阶段 Dockerfile
创建 docker/Dockerfile.uniapp-h5:
# syntax=docker/dockerfile:1
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --legacy-peer-deps --no-audit --no-fund
COPY . .
ARG VITE_APP_BASE_URL=https://api.sbnews.net
ENV VITE_APP_BASE_URL=${VITE_APP_BASE_URL} \
UNIAPP_RELEASE_PATH=/tmp/mobile-release \
NODE_OPTIONS=--max-old-space-size=4096
RUN npm run build:h5
FROM registry.cn-guangzhou.aliyuncs.com/likeadmin/nginx:1.23.1
COPY --from=build /app/dist/build/h5/ /usr/share/nginx/html/
- Step 5: 创建 Admin 静态 Nginx 配置
创建 docker/config/nginx/static/admin.conf:
server {
listen 127.0.0.1:5176;
server_name keislwo.sbnews.net;
root /usr/share/nginx/html;
index index.html;
access_log /var/log/nginx/admin_access.log;
error_log /var/log/nginx/admin_error.log;
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
location = /index.html {
add_header Cache-Control "no-cache";
}
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff2?)$ {
expires 7d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
}
- Step 6: 创建 H5 静态 Nginx 配置
创建 docker/config/nginx/static/uniapp-h5.conf:
server {
listen 127.0.0.1:5177;
server_name web.sbnews.net;
root /usr/share/nginx/html;
index index.html;
access_log /var/log/nginx/h5_access.log;
error_log /var/log/nginx/h5_error.log;
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
location = /index.html {
add_header Cache-Control "no-cache";
}
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff2?)$ {
expires 7d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
}
- Step 7: 静态检查镜像输出路径、生产域名和 SPA 回退
Run:
rg -n "node:20-alpine|VITE_APP_BASE_URL=https://api\.sbnews\.net|/app/dist" docker/Dockerfile.admin docker/Dockerfile.uniapp-h5
rg -n 'listen 127\.0\.0\.1:5176|try_files \$uri \$uri/ /index\.html' docker/config/nginx/static/admin.conf
rg -n 'listen 127\.0\.0\.1:5177|try_files \$uri \$uri/ /index\.html' docker/config/nginx/static/uniapp-h5.conf
git diff --check -- admin/.dockerignore uniapp/.dockerignore docker/Dockerfile.admin docker/Dockerfile.uniapp-h5 docker/config/nginx/static/admin.conf docker/config/nginx/static/uniapp-h5.conf
Expected: 两个镜像、两个监听地址、两个 SPA 回退均匹配;不执行完整前端构建。
- Step 8: 提交前端生产镜像
git add -- admin/.dockerignore uniapp/.dockerignore docker/Dockerfile.admin docker/Dockerfile.uniapp-h5 docker/config/nginx/static/admin.conf docker/config/nginx/static/uniapp-h5.conf
git diff --cached --stat
git commit -m "build: add production frontend images"
Expected: 提交只包含六个前端容器相关文件。
Task 5: 创建统一生产 Compose
Files:
-
Create:
docker/docker-compose.prod.yml -
Step 1: 创建生产 Compose 文件
创建 docker/docker-compose.prod.yml:
name: sport-era-prod
x-logging: &default-logging
driver: json-file
options:
max-size: "20m"
max-file: "5"
x-php-service: &php-service
image: sport-era-prod-php:local
build:
context: ..
dockerfile: docker/Dockerfile.php
restart: unless-stopped
network_mode: host
working_dir: /likeadmin_php/server
environment:
TZ: Asia/Shanghai
volumes:
- ${SERVER_DIR:-../server}:/likeadmin_php/server
- ${SERVER_ENV_FILE:-./.env.server.production}:/likeadmin_php/server/.env:ro
- ./config/php/conf.d/zz-opcache.prod.ini:/usr/local/etc/php/conf.d/zz-opcache.ini:ro
- ./config/php/php-fpm.prod.conf:/usr/local/etc/php-fpm.d/zz-production.conf:ro
logging: *default-logging
x-crawler-service: &crawler-service
image: sport-era-prod-crawler:local
build:
context: ${CRAWLER_DIR:-./crawler}
dockerfile: Dockerfile
restart: unless-stopped
network_mode: host
env_file:
- ${CRAWLER_ENV_FILE:-./.env.crawler.production}
environment:
TZ: Asia/Shanghai
DQD_CONFIG_PATH: /app/config/settings.yaml
DQD_TASKS_FILE: /app/config/crawler_tasks.yaml
DQD_LOCK_DIR: /app/data/locks
DQD_DB_HOST: 127.0.0.1
DQD_REDIS_HOST: 127.0.0.1
volumes:
- ${CRAWLER_DIR:-./crawler}/logs:/app/logs
- ${CRAWLER_DIR:-./crawler}/data:/app/data
logging: *default-logging
services:
server:
<<: *php-service
healthcheck:
test:
- CMD-SHELL
- >-
php -r '$$socket=@fsockopen("127.0.0.1",9000,$$errno,$$errstr,2);
exit($$socket ? 0 : 1);'
interval: 15s
timeout: 5s
retries: 5
start_period: 20s
scheduler:
<<: *php-service
command:
- sh
- -c
- while true; do php think crontab; sleep 60; done
crawler:
<<: *crawler-service
kb-worker:
<<: *crawler-service
command:
- python
- scripts/kb_worker.py
- --batch=50
- --block-ms=5000
- --max-retries=3
api-nginx:
image: registry.cn-guangzhou.aliyuncs.com/likeadmin/nginx:1.23.1
restart: unless-stopped
network_mode: host
depends_on:
server:
condition: service_healthy
volumes:
- ${SERVER_DIR:-../server}:/likeadmin_php/server:ro
- ./config/nginx/conf.d/sport-era.prod-api.conf:/etc/nginx/conf.d/default.conf:ro
- ./log/nginx/prod-logs:/logs
healthcheck:
test:
- CMD-SHELL
- wget -qO- http://127.0.0.1:8000/healthz | grep -q '^ok$'
interval: 15s
timeout: 5s
retries: 5
start_period: 10s
logging: *default-logging
admin:
image: sport-era-prod-admin:local
build:
context: ../admin
dockerfile: ../docker/Dockerfile.admin
args:
VITE_APP_BASE_URL: ${PROD_API_BASE_URL:-https://api.sbnews.net}
restart: unless-stopped
network_mode: host
volumes:
- ./config/nginx/static/admin.conf:/etc/nginx/conf.d/default.conf:ro
healthcheck:
test:
- CMD-SHELL
- wget -qO- http://127.0.0.1:5176/healthz | grep -q '^ok$'
interval: 15s
timeout: 5s
retries: 5
start_period: 10s
logging: *default-logging
uniapp-h5:
image: sport-era-prod-uniapp-h5:local
build:
context: ../uniapp
dockerfile: ../docker/Dockerfile.uniapp-h5
args:
VITE_APP_BASE_URL: ${PROD_API_BASE_URL:-https://api.sbnews.net}
restart: unless-stopped
network_mode: host
volumes:
- ./config/nginx/static/uniapp-h5.conf:/etc/nginx/conf.d/default.conf:ro
healthcheck:
test:
- CMD-SHELL
- wget -qO- http://127.0.0.1:5177/healthz | grep -q '^ok$'
interval: 15s
timeout: 5s
retries: 5
start_period: 10s
logging: *default-logging
- Step 2: 用示例环境文件展开 Compose
Run:
$env:SERVER_ENV_FILE = './.env.server.production.example'
$env:CRAWLER_ENV_FILE = './.env.crawler.production.example'
docker compose -f docker/docker-compose.prod.yml config -q
$compose = docker compose -f docker/docker-compose.prod.yml config
$compose | Select-String '127.0.0.1:8000|127.0.0.1:5176|127.0.0.1:5177|https://api.sbnews.net|scripts/kb_worker.py'
Remove-Item Env:SERVER_ENV_FILE -ErrorAction SilentlyContinue
Remove-Item Env:CRAWLER_ENV_FILE -ErrorAction SilentlyContinue
Expected: config -q 无输出且退出码为 0;展开内容包含三个回环入口、正式 API 域名和 KB Worker 命令。
- Step 3: 确认 Compose 没有数据库服务与公网端口映射
Run:
$env:SERVER_ENV_FILE = './.env.server.production.example'
$env:CRAWLER_ENV_FILE = './.env.crawler.production.example'
$compose = docker compose -f docker/docker-compose.prod.yml config
$compose | Select-String '^ (mysql|redis):' -CaseSensitive
$compose | Select-String '^ ports:' -CaseSensitive
Remove-Item Env:SERVER_ENV_FILE -ErrorAction SilentlyContinue
Remove-Item Env:CRAWLER_ENV_FILE -ErrorAction SilentlyContinue
Expected: 两次 Select-String 都没有输出,因为生产 Compose 不创建 MySQL/Redis,也不使用 Docker ports 发布端口。
- Step 4: 提交统一生产 Compose
git add -- docker/docker-compose.prod.yml
git diff --cached --stat
git commit -m "build: add unified production compose"
Expected: 提交只包含 docker/docker-compose.prod.yml。
Task 6: 编写生产部署与宝塔反向代理说明
Files:
-
Create:
docs/生产环境Docker部署说明.md -
Modify:
docs/业务进度管理.md -
Step 1: 创建生产部署说明
创建 docs/生产环境Docker部署说明.md,内容必须包含以下完整章节与命令。
文件开头:
# 生产环境 Docker 部署说明
## 环境边界
- 生产目录:`/www/wwwroot/sbnews`
- Git 分支:`dev.1.0.0`
- API:`api.sbnews.net`
- Admin:`keislwo.sbnews.net`
- H5:`web.sbnews.net`
- MySQL、Redis、域名和 HTTPS 由宿主机/宝塔管理。
- Docker Compose 负责 Server、Scheduler、Crawler、KB Worker、Admin 和 H5。
首次部署命令:
cd /www/wwwroot
cd /www/wwwroot/sbnews
git switch dev.1.0.0
git pull --ff-only origin dev.1.0.0
cp docker/.env.server.production.example docker/.env.server.production
cp docker/.env.crawler.production.example docker/.env.crawler.production
chmod 600 docker/.env.server.production docker/.env.crawler.production
# 使用编辑器填写真实密码及 API Key,不要把真实配置提交到 Git。
sudo docker compose -f docker/docker-compose.prod.yml config -q
sudo docker compose -f docker/docker-compose.prod.yml up -d --build
日常更新命令:
cd /www/wwwroot/sbnews
git status --short
git tag -f production-stable "$(git rev-parse HEAD)"
git switch dev.1.0.0
git pull --ff-only origin dev.1.0.0
sudo docker compose -f docker/docker-compose.prod.yml config -q
sudo docker compose -f docker/docker-compose.prod.yml up -d --build
三个宝塔站点的反向代理核心配置分别为:
# api.sbnews.net
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
# keislwo.sbnews.net
location / {
proxy_pass http://127.0.0.1:5176;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# web.sbnews.net
location / {
proxy_pass http://127.0.0.1:5177;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
验收命令:
sudo docker compose -f docker/docker-compose.prod.yml ps
curl -f http://127.0.0.1:8000/api/index/config
curl -I http://127.0.0.1:5176/
curl -I http://127.0.0.1:5177/
curl -I https://api.sbnews.net/api/index/config
curl -I https://keislwo.sbnews.net/
curl -I https://web.sbnews.net/
日志和回滚命令:
sudo docker compose -f docker/docker-compose.prod.yml logs --tail 100 server api-nginx scheduler crawler kb-worker admin uniapp-h5
git status --short
git switch --detach production-stable
sudo docker compose -f docker/docker-compose.prod.yml up -d --build
# 回到生产分支
git switch dev.1.0.0
文档还必须明确:不自动导入 SQL、不自动修改宿主机 MySQL/Redis、不在 Git 中保存真实密钥、密码公开后应轮换。
- Step 2: 在业务进度中追加生产编排记录
在 docs/业务进度管理.md 的“已完成事项”顶部追加:
### 100. 生产环境统一 Docker Compose
- 状态:配置已完成,待生产服务器部署验收 - 时间:2026-08-02
完成内容:
- 新增生产 Compose,统一运行 Server、API Nginx、Scheduler、Crawler、KB Worker、Admin 构建版和 uni-app H5 构建版。
- 生产容器使用 host 网络连接宿主机 MySQL `3300` 与 Redis `6377/DB2`,数据库和缓存继续由宿主机管理。
- Admin 与 H5 使用 Node 多阶段构建和独立静态 Nginx,由宝塔分别反向代理三个正式域名。
- 新增生产私有环境模板、回环端口限制、健康检查、日志轮转、部署验证和回滚说明。
涉及模块:
- `docker/docker-compose.prod.yml`
- `docker/Dockerfile.admin`
- `docker/Dockerfile.uniapp-h5`
- `docker/config/nginx/`
- `docker/config/php/`
- `docs/生产环境Docker部署说明.md`
保留该文件现有未提交内容,不覆盖其他协作者的进度记录。
- Step 3: 验证文档不包含真实密钥
Run:
rg -n "change_me|\.env\.server\.production|\.env\.crawler\.production|127\.0\.0\.1:(8000|5176|5177)" docs/生产环境Docker部署说明.md
rg -n "生产环境统一 Docker Compose|docker-compose\.prod\.yml" docs/业务进度管理.md
git diff --check -- docs/生产环境Docker部署说明.md docs/业务进度管理.md
Expected: 文档中的模板、端口和进度记录均匹配;git diff --check 无输出。
- Step 4: 提交部署说明,谨慎处理已有进度文档改动
先运行:
git diff -- docs/业务进度管理.md
git status --short
如果 docs/业务进度管理.md 仍包含其他协作者的未提交内容,只提交新的独立部署说明:
git add -- docs/生产环境Docker部署说明.md
git diff --cached --stat
git commit -m "docs: add production docker deployment guide"
此时保留业务进度文档中的新增记录在工作区,不要把其他协作者的内容混入提交。如果该文件在执行时已经干净,则同时暂存并提交它。
Task 7: 最终静态评审与交付
Files:
-
Review:
.gitignore -
Review:
server/config/cache.php -
Review:
uniapp/src/config/index.ts -
Review:
docker/docker-compose.prod.yml -
Review:
docker/Dockerfile.admin -
Review:
docker/Dockerfile.uniapp-h5 -
Review:
docker/config/nginx/conf.d/sport-era.prod-api.conf -
Review:
docker/config/nginx/static/admin.conf -
Review:
docker/config/nginx/static/uniapp-h5.conf -
Review:
docker/config/php/conf.d/zz-opcache.prod.ini -
Review:
docker/config/php/php-fpm.prod.conf -
Review:
docker/.env.server.production.example -
Review:
docker/.env.crawler.production.example -
Review:
docs/生产环境Docker部署说明.md -
Step 1: 展开最终 Compose
Run:
$env:SERVER_ENV_FILE = './.env.server.production.example'
$env:CRAWLER_ENV_FILE = './.env.crawler.production.example'
docker compose -f docker/docker-compose.prod.yml config -q
docker compose -f docker/docker-compose.prod.yml config --services
Remove-Item Env:SERVER_ENV_FILE -ErrorAction SilentlyContinue
Remove-Item Env:CRAWLER_ENV_FILE -ErrorAction SilentlyContinue
Expected services:
server
scheduler
crawler
kb-worker
api-nginx
admin
uniapp-h5
- Step 2: 检查正式域名与测试域名边界
Run:
rg -n "api\.sbnews\.net|keislwo\.sbnews\.net|web\.sbnews\.net" docker docker/Dockerfile.admin docker/Dockerfile.uniapp-h5 docs/生产环境Docker部署说明.md
rg -n "test-server\.sbnews\.net" docker/docker-compose.prod.yml docker/Dockerfile.admin docker/Dockerfile.uniapp-h5 docker/config/nginx docs/生产环境Docker部署说明.md
Expected: 正式域名存在;生产 Docker 与部署文档中没有测试 API 域名。uniapp/src/config/index.ts 可以保留测试域名作为未传构建变量时的开发默认值。
- Step 3: 检查真实密钥没有进入本次文件
Run:
rg -n --pcre2 "(?i)(password|api_key)\s*=\s*(?!change_me|your_|$)[^#\s]+" docker/.env.server.production.example docker/.env.crawler.production.example docs/生产环境Docker部署说明.md
Expected: 没有匹配,退出码为 1。
- Step 4: 检查差异、提交和无关工作区内容
Run:
git diff --check
git status --short
git log -8 --oneline --decorate
Expected: git diff --check 无输出;本任务真实源码和运行配置均已有独立提交;原有社区、赛事或其他协作者改动仍保持原状态且未混入本任务提交。
- Step 5: 交付服务器执行命令,不在本机自动构建
最终交付应明确生产服务器执行:
cd /www/wwwroot/sbnews
git switch dev.1.0.0
git pull --ff-only origin dev.1.0.0
sudo docker compose -f docker/docker-compose.prod.yml config -q
sudo docker compose -f docker/docker-compose.prod.yml up -d --build
本地任务到此结束,不自动执行 Admin/H5 完整构建,不自动连接或修改生产数据库,也不自动操作宝塔配置。