增加腾讯云生产 Docker 部署编排与一键脚本

- 提供 API/Web 多阶段镜像、Nginx 反代与 WS/文件路径
- docker-compose.prod 含 Postgres、MinIO、API、前端
- deploy.sh 生成密钥、构建启动、可选种子与备份脚本
This commit is contained in:
yml2213
2026-07-15 15:19:04 +08:00
parent 2b18d654b3
commit d744fc790c
8 changed files with 740 additions and 0 deletions
+15
View File
@@ -107,6 +107,21 @@ kefu_sys/
---
## 生产部署(腾讯云 Docker)
单机一键部署(Nginx + API + PostgreSQL + MinIO):
```bash
# 服务器安装 Docker 后
chmod +x deploy/scripts/*.sh
./deploy/scripts/deploy.sh # 首次会生成 deploy/.env.prod,请改公网地址
./deploy/scripts/deploy.sh --seed # 可选:导入演示账号
```
说明见 **[deploy/README.md](deploy/README.md)**(安全组、HTTPS、COS 替换 MinIO、备份升级)。
---
## 本地开发
### 环境要求
+126
View File
@@ -0,0 +1,126 @@
# 生产部署(Docker / 腾讯云 CVM
单机 Docker Compose 方案:`Nginx + API + PostgreSQL + MinIO`,适合腾讯云轻量/ CVM。
## 架构
```
Internet ──:80──▶ web(nginx)
├── / 前端 SPA
├── /api/* → api:8080(含 WebSocket
├── /health → api
└── /files/* → minio:9000/kefu(图片公共读)
```
## 服务器准备(腾讯云)
1. 购买 **CVM / 轻量应用服务器**(建议 2 核 4G 起,系统 Ubuntu 22.04
2. **安全组**放行:`22``80`(有证书再放 `443`
3. 安装 Docker
```bash
# Ubuntu 示例
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker "$USER"
# 重新登录后 docker 无需 sudo
docker compose version
```
4. 上传代码到服务器,例如:
```bash
git clone <你的仓库地址> kefu_sys
cd kefu_sys
```
## 一键部署
```bash
chmod +x deploy/scripts/*.sh
# 首次会生成 deploy/.env.prod 并提示你改域名/公网地址
./deploy/scripts/deploy.sh
# 需要演示账号时(kefu_admin / kefu_admin123 等)
./deploy/scripts/deploy.sh --seed
```
### 必改配置(`deploy/.env.prod`
| 变量 | 示例 | 说明 |
|------|------|------|
| `APP_BASE_URL` | `http://1.2.3.4``https://kefu.xx.com` | 浏览器访问根地址 |
| `STORAGE_PUBLIC_BASE_URL` | `http://1.2.3.4/files` | **必须**与 Nginx `/files` 一致 |
| `HTTP_PORT` | `80` | 宿主机端口 |
| `DB_PASSWORD` / `JWT_SECRET` / `STORAGE_*` | 强随机 | 脚本可自动生成密钥 |
脚本会校验占位符是否改掉。
### 常用命令
```bash
./deploy/scripts/deploy.sh --status # 状态
./deploy/scripts/deploy.sh --down # 停容器(保留卷)
./deploy/scripts/deploy.sh --seed-only # 仅跑种子
./deploy/scripts/backup-db.sh # 备份数据库到 ./backups
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod logs -f api
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod logs -f web
```
## HTTPS 建议(腾讯云)
任选其一:
1. **CLB / CDN** 终结 HTTPS,回源 CVM `80`(最简单)
2. 本机 **Caddy / Nginx + Lets Encrypt** 反代到本 compose 的 `80`
3.`web` 容器映射改到 `8080`,宿主机 Nginx 做 443 → 8080
配置 HTTPS 后,请同步把 `APP_BASE_URL``STORAGE_PUBLIC_BASE_URL` 改成 `https://…`
## 使用腾讯云 COS 替代 MinIO(可选)
1. 创建 COS 桶,开启公共读或使用 CDN 域名
2.`.env.prod` 配置 S3 兼容参数(见 `.env.prod.example` 注释)
3.`docker-compose.prod.yml` 中去掉 `minio` / `minio-init` 依赖,并修改 Nginx,去掉 `/files` 反代(图片直链 COS
当前一键脚本默认 **内置 MinIO**,零额外云产品即可跑通。
## 默认种子账号(仅 --seed 后)
| 角色 | 用户名 | 密码 |
|------|--------|------|
| 平台管理员 | `platform_admin` | `kefu_admin123` |
| 租户管理员 | `kefu_admin` | `kefu_admin123` |
| 坐席 | `agent1` 等 | `kefu_admin123` |
**上线后立即修改密码或关闭 seed 账号。**
## 目录说明
| 路径 | 说明 |
|------|------|
| `docker-compose.prod.yml` | 生产编排 |
| `deploy/docker/Dockerfile.api` | Go API 镜像 |
| `deploy/docker/Dockerfile.web` | 前端构建 + Nginx |
| `deploy/docker/nginx.conf` | 反代与 SPA |
| `deploy/.env.prod.example` | 环境变量模板 |
| `deploy/scripts/deploy.sh` | 一键部署 |
| `deploy/scripts/backup-db.sh` | 数据库备份 |
## 故障排查
| 现象 | 处理 |
|------|------|
| 页面 502 | `docker compose … logs api`,检查 DB 是否 healthy |
| 图片打不开 | 检查 `STORAGE_PUBLIC_BASE_URL` 是否为 `…/files`MinIO 桶是否 public download |
| WebSocket 断 | 确认走同源 `/api/ws`CLB 需开启 WebSocket/长连接 |
| 构建 Go 失败 | 服务器内存不足时加 swap,或本机 build 后 `docker save` 上传 |
## 升级
```bash
git pull
./deploy/scripts/deploy.sh # 重新 build 并滚动启动
./deploy/scripts/backup-db.sh # 升级前建议备份
```
+44
View File
@@ -0,0 +1,44 @@
# 客服云 API — 生产镜像(多阶段构建)
# build context: 仓库根目录
# docker build -f deploy/docker/Dockerfile.api -t kefu-api:latest .
FROM golang:1.24-alpine AS builder
# 若 go.mod 要求更高版本,自动下载 toolchain
ENV GOTOOLCHAIN=auto
ENV CGO_ENABLED=0
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /src
# 先下依赖,利用缓存
COPY server/go.mod server/go.sum ./
RUN go mod download
COPY server/ ./
RUN GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w" -o /out/server ./cmd/main.go
# 种子程序(compose profile seed 使用)
RUN GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w" -o /out/seed ./cmd/seed
FROM alpine:3.21
RUN apk add --no-cache ca-certificates tzdata wget \
&& adduser -D -H -u 10001 kefu
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY --from=builder /out/server /app/server
COPY --from=builder /out/seed /app/seed
USER kefu
EXPOSE 8080
HEALTHCHECK --interval=15s --timeout=5s --start-period=20s --retries=5 \
CMD wget -qO- http://127.0.0.1:8080/health || exit 1
ENTRYPOINT ["/app/server"]
+25
View File
@@ -0,0 +1,25 @@
# 客服云前端 — Nginx 托管静态资源并反代 API / 文件
# build context: 仓库根目录
FROM node:22-alpine AS builder
WORKDIR /web
COPY web/package.json web/package-lock.json ./
RUN npm ci
COPY web/ ./
RUN npm run build
FROM nginx:1.27-alpine
RUN apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo Asia/Shanghai > /etc/timezone
COPY deploy/docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /web/dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=5 \
CMD wget -qO- http://127.0.0.1/healthz || exit 1
+89
View File
@@ -0,0 +1,89 @@
# 生产 NginxSPA + API/WebSocket 反代 + 对象存储公共读路径
# 上游服务名与 docker-compose.prod.yml 中 service 名一致
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream kefu_api {
server api:8080;
keepalive 32;
}
upstream kefu_minio {
server minio:9000;
}
server {
listen 80;
server_name _;
client_max_body_size 20m;
# 上传/长连接
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# 健康探针(不经 API
location = /healthz {
access_log off;
add_header Content-Type text/plain;
return 200 'ok';
}
# 后端健康检查
location = /health {
proxy_pass http://kefu_api/health;
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;
}
# API + WebSocket/api/ws、/api/widget/ws
location /api/ {
proxy_pass http://kefu_api/api/;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_buffering off;
}
# 图片公共访问:浏览器访问 /files/* → MinIO bucket kefu/*
# 须与 STORAGE_PUBLIC_BASE_URL=https://你的域名/files 一致
location /files/ {
proxy_pass http://kefu_minio/kefu/;
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;
# 允许跨域拉取图片(嵌入站点)
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
if ($request_method = OPTIONS) {
return 204;
}
expires 7d;
add_header Cache-Control "public" always;
}
# 前端静态资源
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# 构建产物长缓存
location /assets/ {
root /usr/share/nginx/html;
expires 30d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
}
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# 备份生产 Postgres 数据
# 用法:./deploy/scripts/backup-db.sh [输出目录]
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"
ENV_FILE="deploy/.env.prod"
OUT_DIR="${1:-./backups}"
COMPOSE_FILE="docker-compose.prod.yml"
if [[ ! -f "$ENV_FILE" ]]; then
echo "缺少 $ENV_FILE" >&2
exit 1
fi
# shellcheck disable=SC1090
set -a
source <(sed 's/\r$//' "$ENV_FILE" | grep -v '^\s*#' | grep -v '^\s*$' || true)
set +a
mkdir -p "$OUT_DIR"
STAMP="$(date +%Y%m%d_%H%M%S)"
FILE="${OUT_DIR}/kefu_pg_${STAMP}.sql.gz"
echo "备份到 $FILE"
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec -T postgres \
pg_dump -U "${DB_USER:-kefu}" "${DB_NAME:-kefu_sys}" | gzip > "$FILE"
echo "完成: $FILE ($(du -h "$FILE" | awk '{print $1}'))"
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env bash
# 客服云 — 腾讯云 / 任意 Linux 一键生产部署
# 用法:
# ./deploy/scripts/deploy.sh # 构建并启动
# ./deploy/scripts/deploy.sh --seed # 启动后导入演示账号数据
# ./deploy/scripts/deploy.sh --pull-only # 仅 pull/up,不强制 rebuild(需已有镜像)
# ./deploy/scripts/deploy.sh --down # 停止并移除容器(保留数据卷)
# ./deploy/scripts/deploy.sh --status # 查看状态
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"
COMPOSE_FILE="docker-compose.prod.yml"
ENV_FILE="deploy/.env.prod"
ENV_EXAMPLE="deploy/.env.prod.example"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[deploy]${NC} $*"; }
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
err() { echo -e "${RED}[error]${NC} $*" >&2; }
need_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
err "缺少命令: $1"
exit 1
fi
}
compose() {
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" "$@"
}
rand_hex() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 24
else
head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' | head -c 48
fi
}
ensure_env() {
if [[ ! -f "$ENV_FILE" ]]; then
if [[ ! -f "$ENV_EXAMPLE" ]]; then
err "找不到 $ENV_EXAMPLE"
exit 1
fi
log "未找到 $ENV_FILE,从示例生成…"
cp "$ENV_EXAMPLE" "$ENV_FILE"
local jwt dbpass minio_pass
jwt="$(rand_hex)"
dbpass="$(rand_hex)"
minio_pass="K$(rand_hex | head -c 16)"
# macOS / GNU sed 兼容
if sed --version >/dev/null 2>&1; then
sed -i "s|^JWT_SECRET=.*|JWT_SECRET=${jwt}|" "$ENV_FILE"
sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=${dbpass}|" "$ENV_FILE"
sed -i "s|^STORAGE_SECRET_KEY=.*|STORAGE_SECRET_KEY=${minio_pass}|" "$ENV_FILE"
else
sed -i '' "s|^JWT_SECRET=.*|JWT_SECRET=${jwt}|" "$ENV_FILE"
sed -i '' "s|^DB_PASSWORD=.*|DB_PASSWORD=${dbpass}|" "$ENV_FILE"
sed -i '' "s|^STORAGE_SECRET_KEY=.*|STORAGE_SECRET_KEY=${minio_pass}|" "$ENV_FILE"
fi
warn "已生成随机 JWT_SECRET / DB_PASSWORD / STORAGE_SECRET_KEY"
warn "请编辑 $ENV_FILE,至少设置:"
warn " APP_BASE_URL=http://你的公网IP或域名"
warn " STORAGE_PUBLIC_BASE_URL=http://你的公网IP或域名/files"
echo
read -r -p "设置完成后按回车继续(或 Ctrl+C 退出先改配置): " _
fi
# shellcheck disable=SC1090
set -a
# 去除 Windows CRLF
source <(sed 's/\r$//' "$ENV_FILE" | grep -v '^\s*#' | grep -v '^\s*$' || true)
set +a
if [[ -z "${JWT_SECRET:-}" || "${JWT_SECRET}" == *"请改"* ]]; then
err "请在 $ENV_FILE 中设置有效的 JWT_SECRET"
exit 1
fi
if [[ -z "${DB_PASSWORD:-}" || "${DB_PASSWORD}" == *"请改"* ]]; then
err "请在 $ENV_FILE 中设置有效的 DB_PASSWORD"
exit 1
fi
if [[ -z "${STORAGE_PUBLIC_BASE_URL:-}" || "${STORAGE_PUBLIC_BASE_URL}" == *"你的"* ]]; then
err "请在 $ENV_FILE 中设置 STORAGE_PUBLIC_BASE_URL(如 http://1.2.3.4/files"
exit 1
fi
if [[ -z "${STORAGE_ACCESS_KEY:-}" || -z "${STORAGE_SECRET_KEY:-}" ]]; then
err "请设置 STORAGE_ACCESS_KEY / STORAGE_SECRET_KEY"
exit 1
fi
if [[ ${#STORAGE_SECRET_KEY} -lt 8 ]]; then
err "STORAGE_SECRET_KEY 长度至少 8 位(MinIO 要求)"
exit 1
fi
}
wait_healthy() {
local url="${1:-http://127.0.0.1:${HTTP_PORT:-80}/health}"
local i
log "等待服务就绪: $url"
for i in $(seq 1 60); do
if curl -fsS "$url" >/dev/null 2>&1; then
log "健康检查通过"
return 0
fi
sleep 2
done
err "健康检查超时,请执行: docker compose -f $COMPOSE_FILE --env-file $ENV_FILE logs"
return 1
}
print_summary() {
local base="${APP_BASE_URL:-http://127.0.0.1:${HTTP_PORT:-80}}"
echo
log "========== 部署完成 =========="
echo " 访问地址: ${base}"
echo " 登录页: ${base}/login"
echo " 健康检查: ${base}/health"
echo " Widget 预览: ${base}/widget/preview"
echo " 图片前缀: ${STORAGE_PUBLIC_BASE_URL}"
echo
echo " 常用命令:"
echo " 查看状态 ./deploy/scripts/deploy.sh --status"
echo " 查看日志 docker compose -f $COMPOSE_FILE --env-file $ENV_FILE logs -f"
echo " 导入种子 ./deploy/scripts/deploy.sh --seed-only"
echo " 停止服务 ./deploy/scripts/deploy.sh --down"
echo
echo " 安全提示: 安全组仅放行 80/443;改默认密码;生产务必使用 HTTPS。"
echo "=============================="
}
cmd_up() {
local seed=0 rebuild=1
while [[ $# -gt 0 ]]; do
case "$1" in
--seed) seed=1 ;;
--pull-only) rebuild=0 ;;
*) ;;
esac
shift
done
need_cmd docker
need_cmd curl
if ! docker compose version >/dev/null 2>&1; then
err "需要 Docker Compose V2docker compose"
exit 1
fi
ensure_env
log "拉取基础镜像…"
compose pull postgres minio minio-init || true
if [[ "$rebuild" -eq 1 ]]; then
log "构建并启动全部服务(api + web + db + minio)…"
compose up -d --build --remove-orphans
else
log "启动服务(不强制 rebuild)…"
compose up -d --remove-orphans
fi
wait_healthy "http://127.0.0.1:${HTTP_PORT:-80}/health" || true
wait_healthy "http://127.0.0.1:${HTTP_PORT:-80}/healthz" || true
if [[ "$seed" -eq 1 ]]; then
log "导入演示种子数据…"
compose --profile seed run --rm seed || warn "种子导入失败(可能已存在数据)"
fi
compose ps
print_summary
}
cmd_seed_only() {
need_cmd docker
ensure_env
log "运行 seed…"
compose --profile seed run --rm seed
log "种子完成。默认租户管理员 kefu_admin / kefu_admin123 (若 seed 成功)"
}
cmd_down() {
need_cmd docker
ensure_env
log "停止容器(保留数据卷)…"
compose down
log "已停止。删除数据卷请手动: docker volume rm kefu-prod_kefu_pgdata kefu-prod_kefu_miniodata"
}
cmd_status() {
need_cmd docker
if [[ -f "$ENV_FILE" ]]; then
compose ps
echo
curl -fsS "http://127.0.0.1:${HTTP_PORT:-80}/health" && echo || warn "health 不可达"
else
err "缺少 $ENV_FILE"
exit 1
fi
}
main() {
local action="${1:-up}"
case "$action" in
up|--up|"")
shift || true
cmd_up "$@"
;;
--seed)
# 兼容:./deploy.sh --seed
cmd_up --seed
;;
--seed-only)
cmd_seed_only
;;
--down|down)
cmd_down
;;
--status|status)
# 尝试读 HTTP_PORT
if [[ -f "$ENV_FILE" ]]; then
# shellcheck disable=SC1090
set -a; source <(sed 's/\r$//' "$ENV_FILE" | grep -v '^\s*#' | grep -v '^\s*$' || true); set +a
fi
cmd_status
;;
--pull-only)
cmd_up --pull-only
;;
-h|--help|help)
sed -n '2,12p' "$0"
;;
*)
err "未知参数: $action (见 --help"
exit 1
;;
esac
}
main "$@"
+157
View File
@@ -0,0 +1,157 @@
# 客服云 — 生产 Docker Compose(适合腾讯云 CVM 单机)
# 使用:./deploy/scripts/deploy.sh
# 或:docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod up -d --build
name: kefu-prod
services:
postgres:
image: postgres:16-alpine
container_name: kefu-prod-db
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER:-kefu}
POSTGRES_PASSWORD: ${DB_PASSWORD:?请在 deploy/.env.prod 设置 DB_PASSWORD}
POSTGRES_DB: ${DB_NAME:-kefu_sys}
volumes:
- kefu_pgdata:/var/lib/postgresql/data
networks:
- kefu_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-kefu} -d ${DB_NAME:-kefu_sys}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
minio:
image: minio/minio:latest
container_name: kefu-prod-minio
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${STORAGE_ACCESS_KEY:?请设置 STORAGE_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${STORAGE_SECRET_KEY:?请设置 STORAGE_SECRET_KEY}
volumes:
- kefu_miniodata:/data
networks:
- kefu_net
# 官方镜像极简,无 curl/bash;由 minio-init 重试建桶保证就绪
minio-init:
image: minio/mc:latest
container_name: kefu-prod-minio-init
depends_on:
minio:
condition: service_started
restart: "no"
networks:
- kefu_net
entrypoint: >
/bin/sh -c "
set -e;
i=0;
until mc alias set local http://minio:9000 \"$$MINIO_ROOT_USER\" \"$$MINIO_ROOT_PASSWORD\" 2>/dev/null; do
i=$$((i+1));
if [ $$i -gt 30 ]; then echo 'minio not ready'; exit 1; fi;
echo waiting minio...;
sleep 2;
done;
mc mb -p local/$${STORAGE_BUCKET:-kefu} || true;
mc anonymous set download local/$${STORAGE_BUCKET:-kefu};
echo 'MinIO bucket ready';
"
environment:
MINIO_ROOT_USER: ${STORAGE_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${STORAGE_SECRET_KEY}
STORAGE_BUCKET: ${STORAGE_BUCKET:-kefu}
api:
build:
context: .
dockerfile: deploy/docker/Dockerfile.api
image: kefu-api:${IMAGE_TAG:-latest}
container_name: kefu-prod-api
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
minio-init:
condition: service_completed_successfully
environment:
SERVER_PORT: "8080"
GIN_MODE: ${GIN_MODE:-release}
DB_HOST: postgres
DB_PORT: "5432"
DB_USER: ${DB_USER:-kefu}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME:-kefu_sys}
DB_SSLMODE: ${DB_SSLMODE:-disable}
JWT_SECRET: ${JWT_SECRET:?请设置 JWT_SECRET}
STORAGE_ENDPOINT: minio:9000
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
STORAGE_BUCKET: ${STORAGE_BUCKET:-kefu}
STORAGE_USE_SSL: "false"
STORAGE_REGION: ${STORAGE_REGION:-us-east-1}
# 浏览器访问图片的公网前缀(经 Nginx /files 反代)
STORAGE_PUBLIC_BASE_URL: ${STORAGE_PUBLIC_BASE_URL:?请设置 STORAGE_PUBLIC_BASE_URL,如 https://你的域名/files}
STORAGE_MAX_UPLOAD_MB: ${STORAGE_MAX_UPLOAD_MB:-10}
STORAGE_MAX_IMAGE_EDGE: ${STORAGE_MAX_IMAGE_EDGE:-1920}
STORAGE_WEBP_QUALITY: ${STORAGE_WEBP_QUALITY:-80}
networks:
- kefu_net
# 不对外暴露 8080,统一走 nginx
web:
build:
context: .
dockerfile: deploy/docker/Dockerfile.web
image: kefu-web:${IMAGE_TAG:-latest}
container_name: kefu-prod-web
restart: unless-stopped
depends_on:
api:
condition: service_healthy
minio:
condition: service_started
ports:
- "${HTTP_PORT:-80}:80"
networks:
- kefu_net
# 可选:导入演示数据(profile seed
# docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod --profile seed run --rm seed
seed:
image: kefu-api:${IMAGE_TAG:-latest}
container_name: kefu-prod-seed
profiles: ["seed"]
depends_on:
api:
condition: service_healthy
environment:
DB_HOST: postgres
DB_PORT: "5432"
DB_USER: ${DB_USER:-kefu}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME:-kefu_sys}
DB_SSLMODE: ${DB_SSLMODE:-disable}
JWT_SECRET: ${JWT_SECRET}
STORAGE_ENDPOINT: minio:9000
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
STORAGE_BUCKET: ${STORAGE_BUCKET:-kefu}
STORAGE_USE_SSL: "false"
STORAGE_PUBLIC_BASE_URL: ${STORAGE_PUBLIC_BASE_URL}
entrypoint: ["/app/seed"]
networks:
- kefu_net
restart: "no"
networks:
kefu_net:
driver: bridge
volumes:
kefu_pgdata:
kefu_miniodata: