Files
citywalk-stamp/DEPLOY.md
YANG JIANKUAN 711f422558 feat: 新增 Docker Compose 部署方案
- Dockerfile 单阶段 Alpine 镜像,使用国内镜像(npmmirror / Prisma 引擎 / apk)加速
- entrypoint 首次复制内置图章素材到 uploads volume,自动执行 prisma migrate deploy
- docker-compose.yml 绑定 127.0.0.1:3001,强制走外部 Nginx 反代
- Express 在 production 下同时托管 packages/web/dist 及 SPA fallback
- Prisma schema 增加 linux-musl 二进制目标,支持 Alpine 运行
- 新增 DEPLOY.md 部署指南,含 .env 模板与 Nginx 反代示例

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 17:04:25 +08:00

182 lines
4.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 部署指南
基于 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 <repo-url> 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`