- 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>
182 lines
4.5 KiB
Markdown
182 lines
4.5 KiB
Markdown
# 部署指南
|
||
|
||
基于 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`
|