feat: 交付官方 Docker 运行时,外观改背景图并下线自定义 CSS/JS
站点/后台分轨背景与用户列表排序一并落地;生产 CORS 改走 SITE_URL,健康检查带版本号。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
25
deploy/.env.example
Normal file
25
deploy/.env.example
Normal file
@@ -0,0 +1,25 @@
|
||||
# 官方运行时环境变量。复制为 .env 后填写,不要提交 .env。
|
||||
# 契约见 CONTRACT.md。反代由宿主机 Nginx 等承担,见 nginx.example.conf。
|
||||
|
||||
JIANG13_VERSION=0.1.0
|
||||
|
||||
# 镜像名。未推仓库时 docker compose up --build 会打到这些本地 tag。
|
||||
# 发布后改为 registry.example.com/jiang13-bbs-api 等即可 docker compose pull。
|
||||
JIANG13_IMAGE_API=jiang13-bbs-api
|
||||
JIANG13_IMAGE_WEB=jiang13-bbs-web
|
||||
|
||||
# 对外 origin,须与浏览器地址栏一致(含 https、无尾斜杠)。CORS 兜底。
|
||||
SITE_URL=https://bbs.example.com
|
||||
|
||||
# 发布到宿主机的地址/端口,供本机 Nginx 反代。默认只绑 loopback。
|
||||
JIANG13_BIND=127.0.0.1
|
||||
JIANG13_WEB_PORT=3000
|
||||
JIANG13_API_PORT=3001
|
||||
|
||||
# Postgres(官方 Compose 不把 5432 打到宿主机)
|
||||
POSTGRES_USER=jiang13
|
||||
POSTGRES_PASSWORD=change-me
|
||||
POSTGRES_DB=jiang13
|
||||
|
||||
# 生产必须显式设置。留空则首次启动写入 data/.jwt_secret(依赖 appdata volume)。
|
||||
JWT_SECRET=change-me-to-a-long-random-string
|
||||
90
deploy/CONTRACT.md
Normal file
90
deploy/CONTRACT.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 官方运行时契约
|
||||
|
||||
变更本文件中的服务名、端口、volume、环境变量,视为**运行时大版本**,须在 changelog 写明,并给出最低可升级的应用版本。不要为了省事改本地开发的 `start.bat` / 仓库根 `docker-compose.yml`。
|
||||
|
||||
当前应用版本见仓库根 `VERSION`(现 `0.1.0`)。最低可升级版本:无(首个官方运行时)。
|
||||
|
||||
## 与本地开发的边界
|
||||
|
||||
| | 本地开发 | 官方运行时 |
|
||||
|--|----------|------------|
|
||||
| Compose | 仓库根 `docker-compose.yml`,只起 Postgres,映射 `5432` | `deploy/docker-compose.yml`,项目名 `jiang13` |
|
||||
| 后端 | `cd backend && go run ./cmd/jiang13`,数据 `backend/data/` | 容器 `api`,`JIANG13_WORK_PATH=/var/lib/jiang13` |
|
||||
| 前端 | `cd frontend && npm run dev`,`:3000` | 容器 `web`,Next standalone `:3000` |
|
||||
| 入口 | 浏览器 `http://localhost:3000` | 宿主机反代(Nginx 等)同源反代 `127.0.0.1:3000` + `127.0.0.1:3001` |
|
||||
|
||||
两套 Postgres volume 名字空间不同,可以并存,互不覆盖。官方 Compose **不包含**反代容器。
|
||||
|
||||
## 服务名(Compose)
|
||||
|
||||
- `postgres` — PostgreSQL 17(不映射宿主机 5432)
|
||||
- `api` — Go,容器内 `3001`,默认发布 `127.0.0.1:3001`
|
||||
- `web` — Next.js standalone,容器内 `3000`,默认发布 `127.0.0.1:3000`
|
||||
|
||||
禁止把 API 单独做到另一个子域。`/api` 与 `/api/ws` 必须由反代直达 `api`,不能进 Next(Upgrade 无法经 Next rewrite)。
|
||||
|
||||
## Volume
|
||||
|
||||
| 名 | 容器路径 | 内容 |
|
||||
|----|----------|------|
|
||||
| `pgdata` | `/var/lib/postgresql/data` | 数据库 |
|
||||
| `appdata` | `/var/lib/jiang13/data` | 上传、`private/` 附件、`.jwt_secret` |
|
||||
|
||||
镜像内不放真实 `app.ini`、不放用户数据。
|
||||
|
||||
## 环境变量
|
||||
|
||||
### api
|
||||
|
||||
| 变量 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| `HTTP_PORT` | 否 | 默认 `3001` |
|
||||
| `DEV_MODE` | 是(Compose 写死 `false`) | 生产 cookie 走 `__Host-`,须反代 HTTPS |
|
||||
| `JIANG13_WORK_PATH` | 是 | 固定 `/var/lib/jiang13` |
|
||||
| `DATA_DIR` | 是 | 固定 `/var/lib/jiang13/data` |
|
||||
| `DB_DSN` | 是 | 指向服务名 `postgres` |
|
||||
| `JWT_SECRET` | 是 | 强随机;勿依赖自动生成做多副本 |
|
||||
| `SITE_URL` | 建议 | 对外 origin,无尾斜杠,如 `https://bbs.example.com` |
|
||||
| `CORS_ORIGINS` | 否 | 额外 origin,逗号分隔 |
|
||||
|
||||
### web
|
||||
|
||||
| 变量 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| `BACKEND_URL` | 是 | 固定 `http://api:3001`(SSR / middleware,勿写成公网 URL) |
|
||||
| `PORT` / `HOSTNAME` | 否 | `3000` / `0.0.0.0` |
|
||||
| `NEXT_PUBLIC_APP_VERSION` | 构建期 | 与 `VERSION` 相同;**不要**设置 `NEXT_PUBLIC_API_URL=http://api:3001`(会泄漏到浏览器) |
|
||||
|
||||
浏览器 API 走同源 `/api/*`(由宿主机反代直达 Go)。WebSocket 走同源 `/api/ws`,不要设 `NEXT_PUBLIC_WS_URL`。
|
||||
|
||||
### 宿主机发布
|
||||
|
||||
| 变量 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `JIANG13_BIND` | `127.0.0.1` | 只给本机反代;需要对外直连再改 |
|
||||
| `JIANG13_WEB_PORT` | `3000` | web 宿主机端口 |
|
||||
| `JIANG13_API_PORT` | `3001` | api 宿主机端口 |
|
||||
|
||||
### 镜像 tag
|
||||
|
||||
`JIANG13_VERSION` 同时作为 `api`/`web` 镜像 tag。`JIANG13_IMAGE_API` / `JIANG13_IMAGE_WEB` 默认为本地名;推仓库后改成 registry 路径即可 `docker compose pull`。
|
||||
|
||||
`POSTGRES_PASSWORD` 会拼进 `DB_DSN`,不要包含 `@ : / # ?` 等 URL 保留字符。
|
||||
|
||||
## 健康检查
|
||||
|
||||
- `api`:`GET http://127.0.0.1:3001/health`(含 DB ping、`version`)
|
||||
- `web`:`GET http://127.0.0.1:3000/healthz`(不访问后端)
|
||||
|
||||
## 更新
|
||||
|
||||
应用升级:换同一契约下的新镜像 tag,然后:
|
||||
|
||||
```
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
新 `api` 启动时执行既有 `AutoMigrate`。接受短暂停机。上线前不为双版本并存写兼容垫片。
|
||||
|
||||
改 Compose / 环境变量 / volume 路径属于运行时升级,不能只 pull。
|
||||
32
deploy/Dockerfile.api
Normal file
32
deploy/Dockerfile.api
Normal file
@@ -0,0 +1,32 @@
|
||||
# 构建上下文:仓库根。不含源码与工具链。
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM golang:1.27-bookworm AS build
|
||||
WORKDIR /src
|
||||
ENV GOTOOLCHAIN=local
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
COPY backend/ ./
|
||||
ARG VERSION=dev
|
||||
ARG COMMIT=unknown
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-trimpath \
|
||||
-ldflags="-s -w -X github.com/freefire/jiang13-bbs/version.Version=${VERSION} -X github.com/freefire/jiang13-bbs/version.Commit=${COMMIT}" \
|
||||
-o /out/jiang13 ./cmd/jiang13
|
||||
|
||||
FROM alpine:3.21
|
||||
RUN apk add --no-cache ca-certificates tzdata wget su-exec \
|
||||
&& adduser -D -H -u 1000 jiang13 \
|
||||
&& mkdir -p /var/lib/jiang13/data
|
||||
COPY --from=build /out/jiang13 /usr/local/bin/jiang13
|
||||
COPY deploy/api-entrypoint.sh /usr/local/bin/api-entrypoint.sh
|
||||
RUN sed -i 's/\r$//' /usr/local/bin/api-entrypoint.sh && chmod +x /usr/local/bin/api-entrypoint.sh
|
||||
ENV JIANG13_WORK_PATH=/var/lib/jiang13 \
|
||||
HTTP_PORT=3001 \
|
||||
DEV_MODE=false \
|
||||
TZ=UTC
|
||||
WORKDIR /var/lib/jiang13
|
||||
EXPOSE 3001
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s --retries=5 \
|
||||
CMD wget -qO- http://127.0.0.1:3001/health || exit 1
|
||||
ENTRYPOINT ["/usr/local/bin/api-entrypoint.sh"]
|
||||
37
deploy/Dockerfile.web
Normal file
37
deploy/Dockerfile.web
Normal file
@@ -0,0 +1,37 @@
|
||||
# 构建上下文:frontend/。standalone 产物,运行时不含完整 node_modules。
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:22-bookworm-slim AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --no-audit --no-fund
|
||||
|
||||
FROM node:22-bookworm-slim AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN mkdir -p public
|
||||
ARG VERSION=dev
|
||||
ARG BACKEND_URL=http://api:3001
|
||||
ENV NEXT_TELEMETRY_DISABLED=1 \
|
||||
NODE_ENV=production \
|
||||
NEXT_PUBLIC_APP_VERSION=${VERSION} \
|
||||
BACKEND_URL=${BACKEND_URL}
|
||||
RUN npm run build
|
||||
|
||||
FROM node:22-bookworm-slim AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production \
|
||||
PORT=3000 \
|
||||
HOSTNAME=0.0.0.0 \
|
||||
NEXT_TELEMETRY_DISABLED=1 \
|
||||
BACKEND_URL=http://api:3001
|
||||
RUN mkdir -p public .next && chown -R node:node /app
|
||||
COPY --from=builder --chown=node:node /app/public ./public
|
||||
COPY --from=builder --chown=node:node /app/.next/standalone ./
|
||||
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
|
||||
USER node
|
||||
EXPOSE 3000
|
||||
HEALTHCHECK --interval=10s --timeout=5s --start-period=30s --retries=5 \
|
||||
CMD node -e "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
CMD ["node", "server.js"]
|
||||
74
deploy/README.md
Normal file
74
deploy/README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 官方运行时(Docker Compose)
|
||||
|
||||
一体交付:Next.js SSR + Go API + PostgreSQL 17。 **不含反代**:TLS 与同源路由由你现有的 Nginx / Caddy 处理,示例见 [nginx.example.conf](./nginx.example.conf)。契约见 [CONTRACT.md](./CONTRACT.md)。
|
||||
|
||||
本地开发**不要**用这份 Compose。请继续用仓库根 `start.bat` / `start.sh`(只起 Postgres,前后端热重载)。
|
||||
|
||||
## 首次安装
|
||||
|
||||
1. 安装 Docker Compose v2。
|
||||
2. 复制环境文件并改密码:
|
||||
|
||||
```bash
|
||||
cd deploy
|
||||
cp .env.example .env
|
||||
# 编辑 .env:POSTGRES_PASSWORD、JWT_SECRET、SITE_URL
|
||||
```
|
||||
|
||||
`SITE_URL` 填浏览器看到的 HTTPS origin(如 `https://bbs.example.com`)。`DEV_MODE=false` 会启用 `__Host-` cookie,**必须由你的反代提供 HTTPS**。
|
||||
|
||||
3. 启动(尚未推镜像仓库时从源码构建):
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
默认只把 `127.0.0.1:3000`(web)和 `127.0.0.1:3001`(api)交给本机反代。与本地 `start.bat` 的 3000/3001 冲突时,先停开发进程,或改 `.env` 里的 `JIANG13_WEB_PORT` / `JIANG13_API_PORT`。
|
||||
|
||||
4. 按 [nginx.example.conf](./nginx.example.conf) 把同一 Host 指过来:
|
||||
|
||||
- `/` → web `:3000`
|
||||
- `/api/`(含 `/api/ws`)、`/uploads/`、`/health` → api `:3001`
|
||||
|
||||
`GET https://你的域名/health` 应返回 `"status":"ok"` 和 `version`。空库首次注册的账号自动成为站长。不要把 API 放到另一个子域。
|
||||
|
||||
数据在 Docker volume `appdata` / `pgdata`。不要把 `backend/data` 或真实 `app.ini` 打进镜像。
|
||||
|
||||
## 更新应用(官方路径)
|
||||
|
||||
镜像已推到仓库、且 `.env` 里 `JIANG13_IMAGE_*` 指向该仓库时:
|
||||
|
||||
```bash
|
||||
cd deploy
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
只换应用 tag,不改本目录 Compose / 环境变量表。启动时自动迁移。建议先备份:
|
||||
|
||||
```bash
|
||||
docker compose exec postgres pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" > backup.sql
|
||||
```
|
||||
|
||||
`appdata` 用 `docker volume` 备份。失败则把 `JIANG13_VERSION` 改回上一版再 `up -d`。
|
||||
|
||||
尚未发布镜像时,在更新源码后执行 `docker compose up -d --build`。
|
||||
|
||||
Compose 文件本身有变更时,按新版本 changelog **合并** `deploy/docker-compose.yml` 与 `.env`,不能只 pull。
|
||||
|
||||
## 停止
|
||||
|
||||
```bash
|
||||
cd deploy
|
||||
docker compose down
|
||||
```
|
||||
|
||||
加 `-v` 会删数据库和上传,仅在你确认要清空时使用。不要对仓库根的开发 Postgres 执行这条。
|
||||
|
||||
## 不做本期
|
||||
|
||||
- 后台一键更新
|
||||
- 预编译包 / watchdog
|
||||
- 把 Docker socket 交给 Web 进程
|
||||
- 在 Compose 里内置 Nginx / Caddy
|
||||
- Cloudflare Workers 作为官方运行时
|
||||
6
deploy/api-entrypoint.sh
Normal file
6
deploy/api-entrypoint.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# 以 root 校正 volume 属主后降权运行。数据目录契约:/var/lib/jiang13/data
|
||||
set -e
|
||||
mkdir -p /var/lib/jiang13/data/uploads /var/lib/jiang13/data/private
|
||||
chown -R jiang13:jiang13 /var/lib/jiang13
|
||||
exec su-exec jiang13 /usr/local/bin/jiang13
|
||||
72
deploy/docker-compose.yml
Normal file
72
deploy/docker-compose.yml
Normal file
@@ -0,0 +1,72 @@
|
||||
# 官方运行时契约(变更视为运行时大版本,须写 changelog)
|
||||
#
|
||||
# 本地开发不使用本文件:仓库根 docker-compose.yml 只起 Postgres,
|
||||
# 配合 start.bat / cd backend && go run ./cmd/jiang13 + frontend npm run dev。
|
||||
# 本 Compose 不含反代:页面 :3000、API :3001 交给宿主机 Nginx / Caddy。
|
||||
|
||||
name: jiang13
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-jiang13}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in deploy/.env}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-jiang13}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-jiang13} -d ${POSTGRES_DB:-jiang13}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
api:
|
||||
image: ${JIANG13_IMAGE_API:-jiang13-bbs-api}:${JIANG13_VERSION:-0.1.0}
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile.api
|
||||
args:
|
||||
VERSION: ${JIANG13_VERSION:-0.1.0}
|
||||
COMMIT: ${JIANG13_COMMIT:-unknown}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
HTTP_PORT: "3001"
|
||||
DEV_MODE: "false"
|
||||
JIANG13_WORK_PATH: /var/lib/jiang13
|
||||
DATA_DIR: /var/lib/jiang13/data
|
||||
DB_DSN: postgres://${POSTGRES_USER:-jiang13}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-jiang13}?sslmode=disable
|
||||
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in deploy/.env}
|
||||
SITE_URL: ${SITE_URL:-https://localhost}
|
||||
volumes:
|
||||
- appdata:/var/lib/jiang13/data
|
||||
ports:
|
||||
- "${JIANG13_BIND:-127.0.0.1}:${JIANG13_API_PORT:-3001}:3001"
|
||||
|
||||
web:
|
||||
image: ${JIANG13_IMAGE_WEB:-jiang13-bbs-web}:${JIANG13_VERSION:-0.1.0}
|
||||
build:
|
||||
context: ../frontend
|
||||
dockerfile: ../deploy/Dockerfile.web
|
||||
args:
|
||||
VERSION: ${JIANG13_VERSION:-0.1.0}
|
||||
BACKEND_URL: http://api:3001
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
BACKEND_URL: http://api:3001
|
||||
NODE_ENV: production
|
||||
PORT: "3000"
|
||||
HOSTNAME: 0.0.0.0
|
||||
ports:
|
||||
- "${JIANG13_BIND:-127.0.0.1}:${JIANG13_WEB_PORT:-3000}:3000"
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
appdata:
|
||||
49
deploy/nginx.example.conf
Normal file
49
deploy/nginx.example.conf
Normal file
@@ -0,0 +1,49 @@
|
||||
# 宿主机 Nginx 示例(官方 Compose 不含反代)。
|
||||
# 页面、/api、/api/ws、/uploads 必须同一 Host;/api/ws 直达 Go,不要进 Next。
|
||||
# 把 bbs.example.com 与证书路径换成实际值,upstream 端口与 .env 里 JIANG13_*_PORT 一致。
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name bbs.example.com;
|
||||
|
||||
# ssl_certificate /path/to/fullchain.pem;
|
||||
# ssl_certificate_key /path/to/privkey.pem;
|
||||
|
||||
client_max_body_size 512m;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:3001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_read_timeout 3600s;
|
||||
}
|
||||
|
||||
location /uploads/ {
|
||||
proxy_pass http://127.0.0.1:3001;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location /health {
|
||||
proxy_pass http://127.0.0.1:3001;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user