# 部署指南 基于 Docker Compose 的单容器部署方案。容器内 Express 同时托管: - `/api/*` — API - `/uploads/*` — 图章静态资源 - 其余路径 — React SPA(`packages/web/dist`) 外部通过宿主机 Nginx 反向代理到容器的 3000 端口(仅绑定 127.0.0.1,不直接暴露)。 --- ## 1. 前置条件 - Linux 服务器(Docker ≥ 24、Docker Compose v2) - 已解析到服务器的域名(例 `stamp.example.com`) - 已签发的 SSL 证书(Let's Encrypt / acme.sh 等任选) --- ## 2. 拉取代码并配置环境变量 ```bash git clone citywalk-stamp cd citywalk-stamp cp .env.production.example .env vim .env # 填入 JWT_SECRET / ADMIN_API_KEY / SITE_URL ``` 必填项: | 变量 | 说明 | |---|---| | `JWT_SECRET` | 用户 JWT 签名密钥,建议 `openssl rand -hex 32` 生成 | | `ADMIN_API_KEY` | 管理后台访问密钥,同上 | | `SITE_URL` | 对外域名,例 `https://stamp.example.com`(影响 QR 码链接) | --- ## 3. 启动容器 ```bash docker compose up -d --build ``` 首次启动会自动完成: - Prisma migrate deploy(建表) - 把内置的 16 枚图章素材复制到 `./uploads/stamps/` 查看日志确认启动成功: ```bash docker compose logs -f app # 看到 "Server running on http://localhost:3000" 即 OK ``` --- ## 4. 首次导入图章数据 容器默认不自动 seed 数据库(避免覆盖后续运营数据)。首次部署需手动执行一次: ```bash docker compose exec app pnpm db:seed ``` > 再次执行会**清空并重建**所有图章,同时级联删除用户的收集记录。生产环境请勿随意重跑。 --- ## 5. 配置 Nginx 反向代理 在宿主机上配置 Nginx: ```nginx server { listen 80; server_name stamp.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name stamp.example.com; ssl_certificate /etc/letsencrypt/live/stamp.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/stamp.example.com/privkey.pem; # 单入口反代:容器里 Express 自己处理 /api、/uploads、SPA 路由 location / { proxy_pass http://127.0.0.1:3001; 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; } # 静态资源缓存(可选) location ^~ /uploads/ { proxy_pass http://127.0.0.1:3001; proxy_set_header Host $host; expires 7d; add_header Cache-Control "public, max-age=604800"; } # 上传体积(管理后台上传图章) client_max_body_size 10m; } ``` 测试并 reload: ```bash sudo nginx -t && sudo systemctl reload nginx ``` 访问 `https://stamp.example.com` 即可看到首页。 --- ## 6. 数据持久化与备份 容器两个 bind mount: - `./data/` — SQLite 数据库(`prod.db`) - `./uploads/` — 图章图片(含首次填充的 16 枚 + 后续管理后台上传的) 备份: ```bash # 停机备份最稳妥(SQLite 文件锁) docker compose stop app tar czf backup-$(date +%F).tgz data/ uploads/ .env docker compose start app ``` --- ## 7. 常用运维命令 ```bash # 查看日志 docker compose logs -f app # 进入容器 docker compose exec app sh # 重启 docker compose restart app # 升级(拉新代码后重建) git pull docker compose up -d --build # 查看当前图章数 docker compose exec app sh -c 'echo "SELECT COUNT(*) FROM Stamp;" | sqlite3 /app/data/prod.db' \ || docker compose exec app node -e " import('@prisma/client').then(async ({PrismaClient}) => { const p = new PrismaClient(); console.log(await p.stamp.count()); await p.\$disconnect(); }); " # 完全重置(会删除所有用户和收集数据,谨慎) docker compose down rm -rf data/ uploads/ docker compose up -d --build docker compose exec app pnpm db:seed ``` --- ## 8. 故障排查 - **容器启动失败 `PrismaClientInitializationError`**:检查 `./data/` 目录权限、`DATABASE_URL` 值 - **图章图片 404**:进入容器 `ls /app/packages/server/uploads/stamps` 看是否被 seed 进来;若空,手动执行 `cp /app/stamps-seed/*.jpg /app/packages/server/uploads/stamps/` - **QR 码扫出来是 `localhost`**:`.env` 里 `SITE_URL` 没改对,修正后 `docker compose restart app` - **Nginx 502**:确认容器跑着 `docker compose ps`,端口 `ss -tlnp | grep 3000`